Advanced OS/2 Programming Advanced OS/2 Programming The Microsoft(R) Guide to the OS/2 Kernel For Assembly Language and C Programmers By Ray Duncan PUBLISHED BY Microsoft Press A Division of Microsoft Corporation 16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717 Copyright (C) 1989 by Ray Duncan 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 Duncan, Ray, 1952- Advanced OS/2 programming: the Microsoft guide to the OS/2 kernel for assembly language and C programmers / Ray Duncan p. cm. Includes index. 1. OS/2 (Computer operating system) 2. Assembler language (Computer program language) I. Title. QA76.76.063D8585 1989 88-21103 005.4'469dc19 CIP ISBN 1-55615-045-8 Printed and bound in the United States of America. 1 2 3 4 5 6 7 8 9 FGFG 3 2 1 0 9 8 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., Harmondworth, Middlesex, England Penguin Books Australia Ltd., Ringwood, Victoria, Australia Penguin Books N.Z. Ltd., 182-190 Wairu Road, Auckland 10, New Zealand British Cataloging in Publication Data available IBM(R), PC/AT(R), and PS/2(R) are registered trademarks of International Business Machines Corporation. CodeView(R), Microsoft(R), MS-DOS(R), and XENIX(R) are registered trademarks of Microsoft Corporation. UNIX TM is a trademark of AT&T Bell Laboratories. Acquisitions Editor: Claudette Moore Project Editor: Eric Stroo Technical Editor: Mike Halvorson Dedication For Kathleen Contents Road Map to Important Figures and Tables Acknowledgments Introduction SECTION 1 THE OS/2 OPERATING SYSTEM Chapter 1 Introduction to OS/2 System positioning; Key features of OS/2 Chapter 2 OS/2 Structure and Initialization System components; How OS/2 is loaded; Memory maps Chapter 3 OS/2 Application Programs Program source structure; Module definition files; Executable file structure; Program loading, entry conditions, and termination; Sample program: HELLO.ASM Chapter 4 OS/2 Programming Tools File types and extensions; Macro Assembler; C Compiler; Linker; CREF utility; Librarian; BIND and API.LIB; MAKE utility SECTION 2 PROGRAMMING THE USER INTERFACE Chapter 5 Keyboard and Mouse Input Keyboard modes; Keyboard input methods; Use of logical keyboards; Keyboard subsystems and drivers; Keyboard monitors; Mouse input methods; Mouse subsystems and drivers; Sample program: MOUDEMO.C Chapter 6 The Video Display Video display modes; Using DosWrite for output; Using Vio functions for output; Pop-up support; Video subsystems and drivers Chapter 7 Printer and Serial Ports Printer output methods; The print spooler; Serial port input and output; Configuring the serial port; The serial port and real mode applications SECTION 3 PROGRAMMING MASS STORAGE Chapter 8 File Management Filenames and handles; Opening, creating, and closing files; Reading and writing files; Forcing disk updates; File and record locking; Sample programs: ARGC.ASM, ARGV.ASM, DUMP.C, DUMP.ASM Chapter 9 Volumes and Directories Hierarchical directory structures; Searching disk directories; Directory and disk control; Volume labels; Sample programs: WHEREIS.C and WHEREIS.ASM Chapter 10 Disk Internals Boot sector and BIOS parameter block; File allocation table; Root directory; Files area and subdirectories; Fixed disk partitions; Direct disk access SECTION 4 ADVANCED OS/2 TECHNIQUES Chapter 11 Memory Management Memory protection; Virtual memory; Allocating global memory; Huge memory blocks; Discardable segments; Writable code segments; Local storage Chapter 12 Multitasking The OS/2 scheduler; Managing processes; Managing threads; Managing sessions; Configuring the OS/2 multitasker; Sample programs: TINYCMD.C, TINYCMD.ASM, DUMBTERM.C, DUMBTERM.ASM Chapter 13 Interprocess Communication Semaphores; Pipes; Shared memory; Queues; Signals Chapter 14 IOPL Segments Execution with I/O privilege; IOPL-related API functions; Using IOPL in applications; Sample programs: PORTS.C, PORTS.ASM, PORTIO.ASM Chapter 15 Time, Date, and Timers Reading and setting date and time; Programmed delays; Using timers SECTION 5 CUSTOMIZING OS/2 Chapter 16 Filters System support for filters; Building simple filters; Using a filter as a child process; Sample programs: PROTO.C, PROTO.ASM, FIND.C, EXECSORT.ASM Chapter 17 Device Drivers Unique aspects of OS/2 drivers; Device driver types and modes; Device driver structure; Command code functions; Device Driver Helper functions (DevHlps); Building a device driver; Sample programs: TEMPLATE.ASM, TINYDISK.ASM Chapter 18 Device Monitors Monitor organization; Monitor data packets; Device driver support for monitors; Sample programs: SNAP.C, SNAP.ASM Chapter 19 Dynamic Link Libraries Dynamic linking at load time; Dynamic linking at run time; Writing DLLs in assembly language; Writing DLLs in C; Sample programs: ASMHELP.ASM, SHOWARGS.ASM, CDLL.C, CINIT.ASM SECTION 6 OS/2 REFERENCE Part 1 Kernel API Part 2 DosDevIOCtl Functions Part 3 DevHlp Functions SECTION 7 APPENDIXES Appendix A OS/2 Error Codes Appendix B ASCII and IBM Extended Character Sets Appendix C Resources Appendix D OS/2 Load Module Format Appendix E Module Definition File Syntax Glossary Index Road Map to Important Figures and Tables OS/2 kernel device drivers OS/2 kernel dynlink libraries OS/2 memory maps Segments, groups, and classes Module definition file syntax for applications Application program entry conditions Microsoft MASM switches and options Microsoft C switches and options Microsoft LINK switches and options Video attributes and colors DosOpen modes, flags, and attributes DosFindFirst/DosFindNext search buffer format Boot sector layout Disk directory layout Partition table layout Conforming API functions Time and date formats Device driver attribute word Device driver command codes BIOS parameter block Medium ID bytes Device Driver Helpers (DevHlps) by category Keyboard monitor data packet Mouse and printer monitor data packets Module definition file syntax for dynlink libraries Dynlink library initialization entry conditions Acknowledgments In a book project of this size and duration, the list of people involved at every leveltechnical review, editing, design, word processing, typography, proofreading, and marketinggrows extremely long. Although I cannot thank each of these people individually, I hope they know how much I appreciate their fine and patient work. Special thanks to Pat Combs, Mike Halvorson, Claudette Moore, and Eric Stroo for their editorial contributions, and to David Maritz, Anthony Short, Ben Slivka, and Mark Zbikowski for technical assistance beyond the call of duty. Introduction Operating System/2, Microsoft's protected mode operating system for 80286-based and 80386-based microcomputers, provides programmers with a powerful new platform for application design. It also challenges them to assimilate a body of technical information whose size is unprecedented in the microcomputer world. The reference manuals for OS/2 and its extensions (such as the Presentation Manager and LAN Manager) already fill several shelvesonly a year after the system was first releasedand the Microsoft or IBM Programmer's Toolkit, along with the necessary development tools and libraries, can devour a sizable fixed disk. No single book can cover everything you need to know about OS/2 programming; there are no shortcuts to mastery of such a complex system, and there are few simple answers. This particular book, Advanced OS/2 Programming, focuses solely on the OS/2 kernel and its facilities for character-oriented input and output, file access, virtual memory management, multitasking, interprocess communication, dynamic linking, and the like. Graphically oriented Presentation Manager applications and the Presentation Manager itself are mentioned only in passing; for further information on these topics, see Charles Petzold's excellent book, Programming the OS/2 Presentation Manager (also from Microsoft Press). Advanced OS/2 Programming is essentially divided into six sections, which reflects my prejudices about how the operating system should be studied. Section One is descriptive: It begins with an introduction to the important features of OS/2, followed by an explanation of OS/2's components and initialization, a discussion of the structure of OS/2 application programs, and an operational summary of the Microsoft programming tools. Sections Two and Three address the fundamental concerns of every application programmer: management of the keyboard, display, pointing device, and mass storage. A chapter on the internal structure of FAT (MS-DOScompatible) file systems is also included, although this material will become less important in subsequent versions of OS/2, for which support of installable file systems is planned. Section Four is devoted to virtual memory management, multitasking, interprocess communication, direct hardware control, and programmable timers. For most programmers with an MS-DOS background, this section will be both the most interesting and the most relevant to exploiting fully the capabilities of OS/2. Finally, Section Five contains information on four special classes of OS/2 programs: filters, device monitors, device drivers, and dynlink libraries. Although you will probably never need to write one of these types of programs, you might still find it useful to browse these chapters for a general sense of how such programs work and are put together. The remainder of the bookSection Six and the Appendixesis intended to fill all the reference needs of the typical kernel application programmer. All the OS/2 kernel services made available to application programs, the I/O control subfunctions, and the Device Driver Helper (DevHlp) functions are described with their arguments and results and with cross-references to related functions. The OS/2 error codes, executable file format, and module definition (DEF) file syntax are also covered in detail. Each major OS/2 programming issue and concept is illustrated with complete sample programs in both C and assembly language. For linking the sample programs, OS2.LIB and DOSCALLS.LIB are interchangeable. The programs were written with Solution System's BRIEF editor and assembled or compiled with Microsoft MASM version 5.1 and Microsoft C version 5.1. They have been tested under OS/2 versions 1.0 and 1.1 on a Compaq Portable 286, a Compaq DeskPro 386, and an IBM PS/2 Model 80. As far as I know, they contain no software or hardware dependencies that prevent their running properly on any IBM PC/ATcompatible or PS/2compatible machine running OS/2 version 1.0 or 1.1. To make the sample programs useful to programmers using other languages or non-Microsoft programming tools, I have endeavored to make them as self-contained and "generic" as possible. The source listings do not rely on any header files found in the Microsoft or IBM Programmer's Toolkit, the special reentrant runtime libraries supplied with Microsoft C version 5.1, or the new constructs and directives that were added to Microsoft MASM in version 5.0. For the same reasons, the reference sections shun the macros, arbitrary namings, manifest constants, and customized data types (typedefs) which pervade the Microsoft OS/2 Programmer's Reference. Please feel free to contact me with your questions, comments, and suggestions for future editions via MCI Mail (user name LMI), CompuServe (user ID 72406,1577), or BIX (user name rduncan). Ray Duncan Los Angeles, California December 1988 SECTION 1 THE OS/2 OPERATING SYSTEM Chapter 1 Introduction to OS/2 OS/2 is the Microsoft multitasking, virtual memory, single-user operating system for personal computers based on the Intel 80286 and 80386 microprocessors. It is the first software product to be brought to market as a result of the Joint Development Agreement signed by IBM and Microsoft in August 1985. OS/2 is positioned between the MS-DOS single-tasking operating system and the XENIX/UNIX multi-user multitasking operating system (Figure 1-1 on the following page). Although it is compatible with MS-DOS file systems and can run many existing MS-DOS applications, and although it has a hierarchical directory structure, I/O redirection, and some interprocess communication mechanisms similar to XENIX/UNIX, OS/2 is neither an overblown MS-DOS nor a stripped-down XENIX/UNIX. It is a completely new operating system, designed to support high performance, intensely interactive applications in a business environment. During the next few years, the increasing dominance of 80286- and 80386-based machines, and the accompanying penetration of OS/2, will have a vast influence on the work habits and productivity of computer users. It will also force many changes on programmers and software publishers. Although porting existing MS-DOS products to OS/2 is a straightforward process, taking full advantage of OS/2's capabilities will involve a wholesale rethinking and redesign of most applications. In return, OS/2 offers programmers a "clean slate" and the opportunity to set new standards. ͻ 8086/88/286/386-based personal computers MS-DOS 4.0 Single-tasking Single-user, character interface ͼ ͻ MS-DOS 4.0 8086/88/286/386-based personal computers and Nonpreemptive multitasking Windows 2.1 Single-user, graphical interface ͼ Ŀ 80286/386-based personal computers MS OS/2 1.0 Preemptive multitasking Single-user, character interface Ŀ MS OS/2 1.1 80286/386-based personal computers and Preemptive multitasking Presentation Manager Single-user, graphical interface ͻ 80286/386-based personal computers XENIX Preemptive multitasking Multi-user, character interface ͼ Figure 1-1. The Microsoft family of operating systems for personal computers based on the Intel 80x86 line of microprocessors. Key Features of OS/2 From the programmer's point of view, the key features of software development under OS/2 are the following: A graphical user interface A new Application Program Interface (API) Memory protection and virtual memory Preemptive multitasking Interprocess communication facilities Dynamic linking MS-DOS compatibility These features have many important implications for the structure of the operating system itself, and for the design and operation of application programs. Graphical User Interface The OS/2 graphical user interface is called the Presentation Manager. With Presentation Manager facilities, the user can view multiple applications at the same time in windows (subdivisions of the screen), whose size, shape, and position the user can control. The Presentation Manager offers the programmer a wide variety of vector and raster graphics operations and extensive font support, including multiple font styles and sizes; it allows the development of hardware-independent applications that can run on a broad range of all-points-addressable (APA) output devices. Application programs written to run under the Presentation Manager have a common appearance and style of interaction, using pull-down menus, scroll bars, and dialog boxes. The user can select from menus with either a mouse or the keyboard, and can cut and paste data and graphic images from the domain of one program into another in a "natural" manner. These features help users work more efficiently, and they shorten the learning curve for new application programs. The Application Program Interface The OS/2 kernelthe part of the operating system that manages files, memory, multitasking, timers, and interprocess communicationsprovides about 250 services to application programs. The Presentation Manager supports an additional 500-odd functions related to window management and the graphical user interface. These services, known collectively as the Application Program Interface (API), are invoked by far calls to individual named entry points. Parameters for API functions are passed on the stack, and values are returned in variables (commonly structures) in the caller's memory space. Addresses are always passed or returned in the form of far pointers (selector and offset). Although the OS/2 API is a considerable architectural change from the familiar INT 21H of MS-DOS, its advantages are significant. The API allows OS/2 to take full advantage of the 80286's hardware mechanisms for parameter passing and to enforce the separation between kernel and user privilege levels. It is compatible with the procedure-calling conventions of most high level languages, allowing programs written in high level languages to access operating system services directly without the need for intermediary library functions. And the API can easily be extended for future 32-bit versions of the operating system that will take greater advantage of the 80386 and its successors. Memory Protection and Virtual Memory The CPUs in the Intel 80x86 family generate memory addresses by combining the contents of segment registers (which can be thought of as base pointers) with an offset, or displacement. The offset can be specified in the machine instruction or in one or more index registers (or in both places). The way that the hardware interprets a value in a segment register depends on whether the processor is running in real mode or protected mode. In real mode, the value in a segment register is a paragraph address: a 20-bit physical address divided by 16. To form a complete memory address, the processor shifts the contents of a segment register to the left four bits and adds a 16-bit offset. In real mode, the hardware provides no memory protection mechanisms; any program can read or write any memory address or access any I/O port. MS-DOS and its applications execute in real mode, regardless of the type of processor. In protected mode, a level of addressing indirection is added. Segment registers do not point directly to the base of an area of memory; instead, they contain values called selectors. A selector is an index to an entry in a descriptor table; the entry contains the base address and length of a memory segment, its attributes (executable, read-only, or read-write), and privilege information. Each time a program references memory, the hardware uses information from the descriptor table to generate the physical address and simultaneously validates the memory access. OS/2 executes on the 80286 or 80386 in protected mode; the 8086 and 8088 processors do not support protected mode, which is the reason they cannot run OS/2. Because only the operating system can manipulate descriptor tables, which govern the addressable memory space of all programs, a protected mode operating system can completely isolate programs from one another. If a program attempts to read or write memory that does not belong to it, or if it calls a routine to which it has not been given access, a CPU fault occurs and the operating system regains control. The presence of this memory protection makes for a very robust programming and debugging environment: An aberrant program rarely brings down the entire system. Protected mode also provides privilege levels that can be used to constrain application programs. The four hardware-enforced privilege levels are referred to as ring 0, ring 1, ring 2, and ring 3. Applications ordinarily execute at ring 3 (the lowest privilege level); if a ring 3 program attempts to clear interrupts, read or write I/O ports, or modify CPU registers that would compromise memory protection, a CPU fault is generated and the program is terminated. The operating system kernel executes at ring 0, which provides unrestricted access to all instructions, memory addresses, and I/O ports. OS/2 does not use ring 1 at all. A limited class of applications that require direct access to adapters (such as nonPresentation Manager graphics applications) are allowed to run at ring 2 and perform I/O operations. The flip side of the memory protection coin is virtual memory. OS/2 can manage as much as 16 MB of physical memory, but the amount of installed RAM is nearly irrelevant to the average application program running in protected mode. When the sum of the memory owned by active programs in the system exceeds the actual amount of physical memory, memory segments are rolled in and out as needed from a swap file (or simply discarded and reloaded in the case of code segments or read-only data segments). This segment swapping is accomplished by an OS/2 module known as the Memory Manager, with the aid of the processor's hardware memory protection mechanisms, and is completely invisible to application programs. The theoretical limit on the amount of memory a program can own or share is about 128 MB (in OS/2 version 1.1), but the practical limit is the amount of physical RAM plus the free space on the logical drive that contains the swap file. Preemptive Multitasking Preemptive multitasking (sometimes called time-slicing) refers to OS/2's ability to allocate processor time among multiple programs in a manner that is invisible to those programs. A hardware interrupt called the timer tick, generated by a programmable timer chip, allows the operating system to regain control at predetermined intervals. After updating the current date and time, the timer tick interrupt handler transfers control to the scheduler, which maintains a list of the active tasks and their states and decides which piece of code will run next. For the user, the interface to OS/2's multitasking capabilities is simple and easy to understand. A user starts a program by picking its name from a menu or by entering the name at a command prompt. OS/2 programs that are compatible with the Presentation Manager can run within windows, whereas those that are not compatible occupy the full screen. In either case, when more than one program is running, the user can press hot keys or choose from a menu to select a program with which to interact. The programmer can control OS/2 multitasking at three levels of system activity: processes, threads, and sessions. The simplest case of a process is similar to a program loaded under MS-DOS: A process is initiated when the operating system allocates some memory, loads the necessary code and data from a disk file, and gives it control at an entry point that is specified in the file. Subsequently, the process can obtain and release additional resources, such as memory segments, disk files, interprocess communication facilities, and timers. When a process starts, it contains a single point of execution: its primary thread. That thread can in turn create other threads, all of which share equal access to the memory, files, and other resources owned by the process. The OS/2 scheduler deals with threads rather than with processes; for each active thread, it keeps track of the state (executing, ready to execute, or waiting for some event), priority, register contents and flags, and instruction pointer. The ability to decompose an application into multiple asynchronous threads, which can communicate rapidly through a common data space, is an extremely powerful feature of OS/2. Each process is a member of a session (sometimes called a screen group), which is associated with a virtual console: a virtual screen, keyboard, and mouse. When the user chooses a session, the virtual devices are bound to the physical devices, and the user can interact with the programs in that session. OS/2 can support as many as 12 protected mode sessions and 1 real mode user session, and each session can contain one or more processes. (The Presentation Manager and all its applications, for example, run in a single session.) Interprocess Communication OS/2 supports all the methods of interprocess communication (IPC) that are found in other multitasking operating systems: Semaphores are simple IPC objects that have only two states: set (or owned) and cleared (or not owned). They are used between threads or processes for signaling and for synchronization of access to any type of resource. OS/2 supports several different types of semaphores to meet a variety of speed and security needs. Pipes, as in XENIX/UNIX, allow high performance transfer of variable length messages. A process can read and write them much as it does files, except that the data is always kept resident in memory. Unnamed (anonymous) pipes are used by closely related processes running on the same machine, whereas named pipes allow communication between unrelated processes running on the same or different machines. Shared memory segments can be accessed by any process that can reference the segment by name. IPC by shared memory is potentially the fastest available mechanism, because its speed is limited only by the rate at which bytes can be copied from one place to another. Queues are named, global objects and can be thought of as structured lists of shared memory segments. Queue records can be ordered by first-in-first-out (FIFO), last-in-first-out (LIFO), or by an assigned priority, and a queue's size is limited only by the number of available selectors and the disk swap space. Signals are similar to their implementation in XENIX/UNIX; they simulate an interrupt for the receiving process in that they divert its execution immediately to a signal handler. The system provides a default handler for each signal type; the process can register its own handler for a particular type, or it can notify the system to ignore the signal. Dynamic Linking The 80286 and 80386 support for protected, virtual memory makes it possible to place frequently used procedures into special files known as dynamic link (dynlink) libraries. Dynlink libraries are, in essence, shareable subroutine libraries which are loaded on demand. Placing common procedures in dynlink libraries allows those routines to be altered, improved, or replaced without changing the application programs which invoke them. Their use also conserves both RAM and disk storage, because the size of each individual EXE file is reduced, and because the same copy of a dynlink library procedure in memory is shared by all the processes which use it. The dynlink library concept is so powerful and so widely useful that most OS/2 kernel and Presentation Manager functions available to application programs are distributed in this form. Calls from a program to routines in a dynamic link library are resolved in two stages. The Linker is informed that a particular external name is a dynlink routine either by an IMPORTS statement in the program's module definition file or by the occurrence of a special dynlink reference record in an object module library. It then builds the information necessary for dynamic linking into the program's EXE file header: the names of the dynlink routines that are needed, the names of the dynlink library files in which those routines are found, and a list for each routine of all the addresses within the program at which it is called. When the application is loaded for execution, OS/2 examines the list of imported routines, fetches from disk any external routines that are not already resident in memory, and fixes up the addresses within the calling program. This technique is sometimes called late binding. MS-DOS Compatibility OS/2 provides upward compatibility and a smooth transition from MS-DOS at three levels: the user interface, the file system, and the DOS compatibility mode (sometimes called the 3.x Box). The command line interface of OS/2 is virtually identical to that of MS-DOS versions 2.x and 3.x, with the exception of a few new or enhanced commands, batch file directives, and CONFIG.SYS file options. Similarly, the "desktop" of the OS/2 Presentation Manager, with its overlapping windows, pull-down menus, and dialog boxes, closely resembles the visual shell of MS-DOS version 4. The file structure for both flexible and fixed disksthat is, the layout of the partition table, directories, file allocation tables, and the files areais exactly the same for the initial release of OS/2 as it is for MS-DOS. Developers can therefore exchange files and move back and forth between the two environments with a minimum of difficulty. A future release of OS/2 will support installable file systems, which will relieve users of the historical MS-DOS limitations on volume sizes and filename lengths. DOS compatibility mode is a component of the OS/2 operating system that allows a single "old," real mode (MS-DOS) application to run alongside "new," protected mode applications. OS/2 traps MS-DOS function calls by the real mode application and translates them into API calls, switching back and forth between real mode and protected mode as necessary to perform I/O and other services. DOS compatibility mode also provides a realistic-looking milieu for more hardware-dependent MS-DOS programs by supporting certain "undocumented" MS-DOS services and internal flags, by supplying a "clock tick" interrupt at the appropriate frequency, by maintaining a ROM BIOS data area at segment 40H, and so forth. The user can determine how much memory the system allocates to DOS compatibility mode by adding a directive in the CONFIG.SYS file, or the user can disable the mode completely. One disadvantage of supporting compatibility mode is that the entire system is left vulnerable. "Ill-behaved" MS-DOS programs can crash the system, an unfortunate trade-off for allowing the real mode programs to be used at all. Chapter 2 OS/2 Structure and Initialization The software that runs on present-day computer systems is, by convention, organized into layers with varying degrees of independence from the characteristics of the underlying hardware. The objectives of this layering are to minimize the impact on programs of differences among hardware devices or changes in the hardware, to centralize and optimize the code for common operations, and to simplify the task of moving programs and their data from one machine to another. To give you a feel for the territory, let's divide the software in a system into three layers, as shown in Figure 2-1 on the following page. In this simplified view of a system, the top layer is the least hardware-dependent. It is composed of application programs, which perform a specific job, interact with the user, and manipulate data in units of files and records. Such programs are sometimes called transient because they are brought into RAM (random access memory) for execution when they are needed and then discarded from memory when their job is finished. The middle layer is the operating system kernel, which allocates system resources such as memory and peripheral devices, implements the file system, and provides a host of hardware-independent services to application programs. The kernel is usually brought into memory from disk when you turn on or restart the system, and it remains fixed in memory until the system is turned off. The lowest software layer is made up of specialized programs called device drivers. Device drivers are responsible for transferring data between a peripheral device (such as a disk or terminal) and RAM, where it can be used by other programs. Drivers shield the operating system kernel from the need to deal with the I/O port addresses of the hardware, its operating characteristics, and the peculiarities of a particular peripheral device, just as the kernel in turn shields application programs from the details of memory and file management. With these basic concepts in hand, let's take a look at the actual organization of OS/2 (still recognizing, however, that the real operating system is more complicated than either model). ĿĿ Applications  Ŀ Operating system kernel Software  Ŀ Device drivers  ͻ Hardware ͼ Figure 2-1. A simplistic view of OS/2 system layers. The Structure of OS/2 The major elements of the OS/2 operating system are the following: Device drivers Kernel Dynlink libraries Command processor Presentation Manager The operating system components interact in complicated ways, and a particular system capability is not always located where you might expect. Some OS/2 components execute at the kernel level (ring 0), some in user mode with I/O privilege (ring 2), and some at the lowest privilege level (ring 3), yet this distinction is generally invisible to application programs (Figure 2-2). Ŀ Ŀ Ŀ Ŀ Kernel Kernel CMD.EXE Presentation ...etc. App #1 App #2 Manager Ŀ Ŀ Ŀ Ŀ Ŀ VIOCALLS KDBCALLS MOUCALLS DOSCALL1 SESMGR ...etc. .DLL .DLL .DLL .DLL .DLL Ŀ Ŀ Ŀ BVSCALLS BKSCALLS BMSCALLS ...etc. .DLL .DLL .DLL Ring 3 ----------------------------------------------------------- Ring 2 ------------------------------------------------------------------ Ring 0 Ŀ OS/2 kernel Ŀ Device drivers ͻ Hardware ͼ Figure 2-2. A less simplistic view of OS/2 system layers. The OS/2 kernel and device drivers run at ring 0 and have unrestricted access to all I/O ports and memory addresses. Application programs and dynlink libraries normally run in ring 3 and cannot access the hardware; ring 3 programs are fully protected from each other. Some application programs and dynlink libraries contain I/O privilege segments and are allowed to perform I/O operations in ring 2; however, servicing of interrupts is reserved for ring 0 routines. Presentation Manager applications and dynlink libraries are not shown here. Device Drivers The basic OS/2 system includes a large number of device drivers, which are supplied in individual files with the SYS extension (Figure 2-3). A "base set" of device drivers for the keyboard, display, floppy disk, fixed disk, printer, and real time clock is always loaded during system initialization. These drivers are selected automatically from the drivers on the boot disk based on the system type (PC/AT or PS/2). The other standard OS/2 drivers, such as ANSI.SYS, MOUSEA01.SYS, POINTDD.SYS, COM01.SYS, and EGA.SYS, are loaded only if the appropriate DEVICE directive is present in the CONFIG.SYS file. Drivers supplied by third party manufacturers can also be loaded during initialization with DEVICE directives. Driver Description ANSI.SYS ANSI console driver for real mode (logical device CON) CLOCK01.SYS Real time clock driver for PC/AT (logical device CLOCK$) CLOCK02.SYS Real time clock driver for PS/2 (logical device CLOCK$) COM01.SYS Serial communications port driver for PC/AT (logical devices COM1 and COM2) COM02.SYS Serial communications port driver for PS/2 (logical devices COM1, COM2, and COM3) COUNTRY.SYS Country-dependent information, such as collating tables and time, date, and currency formats DISK01.SYS Floppy disk and fixed disk driver for PC/AT DISK02.SYS Floppy disk and fixed disk driver for PS/2 EGA.SYS Supplementary EGA display driver for real mode; saves and restores state for the EGA write-only control registers EXTDSKDD.SYS Creates a new logical drive letter for a block device; optionally configures the medium type and number of heads, tracks, and sectors for the device KBD01.SYS Keyboard driver (logical device KBD$) for PC/AT KBD02.SYS Keyboard driver (logical device KBD$) for PS/2 MOUSEA00.SYS Serial interface Mouse Systems PC Mouse driver for PC/AT (logical device MOUSE$) MOUSEA01.SYS Serial interface Visi-On Mouse driver for PC/AT (logical device MOUSE$) MOUSEA02.SYS Serial interface Microsoft Mouse driver for PC/AT (logical device MOUSE$) MOUSEA03.SYS Bus interface Microsoft Mouse driver for PC/AT (logical device MOUSE$) MOUSEA04.SYS Driver for Inport Microsoft Mouse for PC/AT (logical device MOUSE$) MOUSEB00.SYS Serial interface Mouse Systems PC Mouse driver for PS/2 (logical device MOUSE$) MOUSEB01.SYS Serial interface Visi-On Mouse driver for PS/2 (logical device MOUSE$) MOUSEB02.SYS Serial interface Microsoft Mouse driver for PS/2 (logical device MOUSE$) MOUSEB05.SYS Driver for IBM In-board Mouse for PS/2 (logical device MOUSE$) POINTDD.SYS Pointing device cursor driver (called by mouse drivers, logical device POINTER$) PRINT01.SYS Parallel port printer driver (logical devices PRN or LPT1, LPT2, and LPT3) for PC/AT PRINT02.SYS Parallel port printer driver (logical devices PRN or LPT1, LPT2, and LPT3) for PS/2 SCREEN01.SYS Video display driver (logical device SCREEN$) for PC/AT SCREEN02.SYS Video display driver (logical device SCREEN$) for PS/2 VDISK.SYS Electronic or virtual disk (RAM disk) driver Figure 2-3. Device drivers provided with the OS/2 kernel. Device drivers specific to the Presentation Manager are not listed here. The OS/2 kernel communicates with device drivers through I/O request packets; the drivers translate these requests into the proper commands for the peripheral device controllers ("adapters"). In IBM PS/2 systems, the most primitive parts of the hardware drivers are in ROM (read-only memory); these protected mode routines are referred to as the ROM ABIOS ("Advanced Basic I/O System") to differentiate them from the more familiar real mode ROM BIOS. Installable drivers will be discussed in more detail under "How OS/2 Is Loaded" later in this chapter, and in Chapter 17 (Device Drivers). The OS/2 Kernel The kernel implements the fundamental OS/2 services offered to application programs. These services include: File and record management Character device input and output Memory management "Spawning" of other programs Interprocess communication Real time clock services including date, time, and programmable timers MS-DOS emulation Programs access kernel functions through the OS/2 API by pushing parameters on the stack and executing a far call to a named entry point. Use of the API is described further in Chapter 3. The OS/2 kernel is read into memory during system initialization from the file OS2DOS.COM on the boot disk. Dynlink Libraries Dynamic link libraries (also called dynlink libraries, or DLLs) are shareable subroutine libraries that are loaded on demand. In contrast to the routines stored in traditional object libraries, which are bound permanently into an application's executable file, dynlink library routines are bound to an application at its load time. Much of OS/2 is distributed in the form of DLLs (see Figure 2-4). In particular, the entry points for nearly all of the documented OS/2 kernel API routines are present within eight dynlink libraries: DOSCALL1.DLL, KBDCALLS.DLL, MONCALLS.DLL, MSG.DLL, MOUCALLS.DLL, NLS.DLL, QUECALLS.DLL, and VIOCALLS.DLL. The other dynlink libraries supplied with the kernel, such as SESMGR.DLL or SPOOLCP.DLL, provide specialized functions which normal applications do not need or cannot access. Library Description ANSICALL.DLL Supports ANSI escape sequences for keyboard redefinition and screen control in protected mode screen groups BKSCALLS.DLL Basic keyboard subsystem, called by KBDCALLS.DLL BMSCALLS.DLL Basic mouse subsystem, called by MOUCALLS.DLL BVSCALLS.DLL Basic video subsystem, called by VIOCALLS.DLL DOSCALL1.DLL Contains entry points for most of the DOS API functions, including those for file management, time and date, memory management, and interprocess communication KBDCALLS.DLL Keyboard subsystem router, contains entry points for KBD API functions; based on the caller's screen group, requests are passed to a keyboard subsystem DLL (such as BKSCALLS.DLL) for processing MONCALLS.DLL Contains entry points for the device monitor API MOUCALLS.DLL Contains entry points for the MOU API functions; based on the caller's screen group, requests are passed to a mouse subsystem DLL (such as BMSCALLS.DLL) for processing MSG.DLL Contains entry points for message management API NLS.DLL Contains entry points for internationalization support API QUECALLS.DLL Contains entry points for queue management API SESMGR.DLL Contains entry points for session management API SPOOLCP.DLL Support for print spooler VIOCALLS.DLL Video subsystem router, contains entry points for the VIO API functions; based on the caller's screen group, requests are passed to a video subsystem DLL (such as BVSCALLS.DLL) for processing Figure 2-4. Dynamic link libraries provided with the OS/2 kernel. DLLs specific to the Presentation Manager are not listed here. The code within a dynlink library runs within the context of the calling program and uses its local descriptor table (LDT). Thus, a library always runs in user mode (ring 3) or, in a few cases, with I/O privilege (ring 2). Code that must run at ring 0 is always located in the kernel or in device drivers and is entered by a call gate from a DLL or (less commonly) directly from an application. The actual location of the code that performs an API function and the mode in which it executes are invisible to applications and subject to change. The Command Processor The command processor provides a line-oriented user interface to the operating system. It parses and carries out user commands for common services, such as copying, renaming, and deleting files; creating and destroying directories; getting and setting the time and date; listing the contents of directories; and starting programs. The default protected mode command processor is found in the CMD.EXE file. You can be running multiple copies of CMD.EXE simultaneously one or more in each protected mode session. Because code (text) segments are always shared in OS/2, the actual memory overhead of each additional copy of CMD.EXE is relatively small. CMD.EXE runs as a normal process in user mode (ring 3) and calls documented API services to carry out its functions. The default real mode command processor is implemented in the file COMMAND.COM, just as under MS-DOS. Because the memory that real mode programs can use is a relatively limited resource, COMMAND.COM is divided into three sectionsresident, initialization, and transient each of which guards its memory allocation to a different degree. The resident section is loaded at the low end of the memory allocated for DOS compatibility mode. It contains the routines to process Ctrl-Break and Ctrl-C entries and critical runtime errors for a real mode program, as well as the routines to terminate such programs. It also contains the code to reload the transient portion of COMMAND.COM when necessary. The initialization section of COMMAND.COM is loaded above the resident part when a real mode session starts. This section processes the AUTOEXEC.BAT file (the user's list of initialization commands for a real mode session) and is then discarded. The transient part of COMMAND.COM is loaded at the high end of the memory allocated for compatibility mode, and real mode programs can use its memory for their own purposes. The transient section issues the user prompt, reads commands from the keyboard or batch file, and causes them to be executed. When a real mode program terminates, the resident portion of COMMAND.COM does a checksum of the transient part to determine whether it has been destroyed and fetches a fresh copy from the disk if necessary. The user commands that CMD.EXE and COMMAND.COM accept fall into three categories: Internal commands External commands Batch files Internal commands are those carried out by code that is embedded in the command processor itself. Commands in this category include COPY, REN(AME), DIR(ECTORY), and DEL(ETE). In the case of the real mode processor, COMMAND.COM, the routines for the internal commands are in the transient portion. External commands are simply names of disk files containing executable programs. The term external command usually refers to a program file distributed on the OS/2 operating system disks; in reality, however, OS/2 does not distinguish between those programs and any other executable programs when it loads them. A batch file is an ASCII text file that contains a list of internal, external, or batch commands. Batch files are processed by a special interpreter, built into the command processor, that reads the file a line at a time and carries out each of the specified operations in order. Batch files used in the protected mode session have the extension CMD. As under MS-DOS, the extension BAT identifies batch files intended for the real mode command processor, which has its batch file interpreter in the transient portion. From a user perspective, CMD.EXE and COMMAND.COM operate almost identically, although CMD.EXE's internal commands and handling of I/O redirection are somewhat more sophisticated. When the user enters a command, the command processor first checks its list of internal commands to see if it can handle the command directly. If not, the command processor searches the current directory and then each directory named in the environment's PATH string for a file with the same name and the extension COM, EXE, or CMD/BAT (in that order). Of course, if the command processor finds a COM or EXE file, the file must have the appropriate type of header (or none for COM files) for the current CPU mode (real or protected). If the search fails for all file types in all possible locations, the system displays an error message. You can replace both the real and protected mode command processors with alternative, customized command processors by adding or changing a line in the CONFIG.SYS file and restarting the system. Specify the command processor for protected mode with the PROTSHELL directive; use the SHELL directive for the real mode command processor. You can also use these directives to designate locations for the command processors other than the root directory of the system boot disk or to identify a program or batch file to execute when the command processor is first loaded. The Presentation Manager The Presentation Manager is the graphical user interface for OS/2 version 1.1; it replaces the character mode Session Manager that was shipped with OS/2 version 1.0 and renders the command processor CMD.EXE largely superfluous. Parts of the Presentation Manager run as normal processes and interact with the user, whereas other parts are distributed among dynlink libraries and provide graphical services to application programs. The Presentation Manager is loaded with a PROTSHELL= statement in the CONFIG.SYS file, which also specifies the protected mode command processor. The three main components of the Presentation Manager's user interface are called Start Programs, Task Manager, and File System. All three run in windows on the Presentation Manager desktop and can be visible simultaneously. The user controls the sizes and positions of the windows, as well as the behavior of the mouse and the colors of the desktop background, text, menus, and title and scroll bars. Start Programs allows the user to start new processes (including command processors) by name from a menu. Well-behaved character-based applications and graphical applications that are written to Presentation Manager conventions run in additional windows, while incompatible applications are run "full-screen" in separate sessions. The Task Manager allows the user to switch between processes and sessions with hot keys or with the mouse. The File System component displays graphical representations of directory trees and lists of directories and files, and it allows files to be copied, moved, renamed, deleted, printed, or selected for execution. The structure and operation of the Presentation Manager is a complex subject that is beyond the scope of this book. For further details, refer to Programming the OS/2 Presentation Manager, by Charles Petzold, published by Microsoft Press. How OS/2 Is Loaded When you power up or reset an 80286 or 80386 CPU, program execution begins in real mode at a predetermined address (FFFF0H for the 80286, FFFFFFF0H for the 80386). This is a hard-wired characteristic of the processor and has nothing to do with OS/2. Computers based on these CPUs are usually designed so that the initial execution address lies within ROM and contains a jump instruction to system test code and the ROM bootstrap routine (Figure 2-5). The ROM bootstrap's job is to read the disk bootstrap into memory from a floppy or fixed disk. The disk bootstrap is a short program that is placed in a disk's boot sector (track 0, head 0, sector 1 for floppy disks) by the FORMAT program. If the read operation is successful, the ROM bootstrap transfers control to the disk bootstrap (Figure 2-6); otherwise, it executes Int 18H to start ROM BASIC. Ŀ 16 MB Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Ĵ 1 KB Interrupt vectors 0 KB Figure 2-5. Memory map when system is turned on or restarted. The CPU begins execution in real mode at location FFFF0H (80286 systems) or FFFFFFF0H (80386 systems); control passes from there to the ROM bootstrap routine. Ŀ 16 MB Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Ĵ Disk bootstrap Ĵ Ĵ 1 KB Interrupt vectors 0 KB Figure 2-6. The ROM bootstrap has read the disk bootstrap into memory from the first sector of the boot drive and given it control. The system is in real mode. The disk bootstrap first checks to see if the boot disk contains a copy of OS/2. It does this by inspecting a table of information (also placed in the boot sector by the FORMAT program) that describes the characteristics of the disk medium, such as the number of tracks, heads, and sectors per track. Using this table, called the BIOS parameter block (BPB), the disk bootstrap can calculate the location of the root directory and then search the directory for the file OS2LDR. If OS2LDR is found, it is read into memory and receives control (Figure 2-7 on the following page). Otherwise, the disk bootstrap displays the following message: The file OS2LDR cannot be found. Insert a system diskette and restart the system. OS2LDR consists almost entirely of a full-fledged loader for segmented executable ("new EXE") files along with a primitive file system built on the ROM BIOS disk driver. OS2LDR first determines the size and location of all contiguous memory below 640 KB and above 1 MB. It then searches the root directory of the boot disk for the file OS2KRNL (which, despite its lack of an extension, is actually a file in the new EXE format) and loads it into memory segment by segment, performing all necessary relocations. Finally, OS2LDR transfers control to OS2KRNL and passes a map of system memory and some other configuration information (Figure 2-8 on page 23). Ŀ 16 MB Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Ĵ OS2LDR Ĵ Ĵ Spent disk bootstrap Ĵ Ĵ 1 KB Interrupt vectors 0 KB Figure 2-7. The disk bootstrap has read the OS2LDR file into memory and given it control. The memory occupied by the disk bootstrap program will be overlaid by other OS/2 modules. The system is still in real mode. The OS2KRNL file is made up of many code and data segments containing the vital modules of the OS/2 kernel. The system initialization module (SysInit) receives control when OS2KRNL is first entered. SysInit's first job is to establish protected mode operation: It creates the necessary global descriptor table (GDT), interrupt descriptor table (IDT), task state segment (TSS), and so on, after which it sets the PE bit in the machine status word (CR0) to switch modes. Creation of a valid IDT permits the system to initialize the two 8259 programmable interrupt controllers (PICs), which enable and service interrupts. SysInit then proceeds with the remaining CPU and memory initialization by moving the various kernel modules to their final locations. To provide a maximum amount of memory for real mode operations, it leaves only the most critical kernel components, such as the scheduler, in the 0640 KB region ("System Low"); the remaining components, such as the file system and signal handlers, are placed above the 1 MB boundary ("System High"). It then initializes the physical memory manager (PMM), virtual memory manager (VMM), and the system memory arena (the area of dynamically allocatable memory). Ŀ 16 MB Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Ĵ OS2KRNL Ĵ Ĵ OS2LDR Ĵ Ĵ 1 KB Interrupt vectors 0 KB Figure 2-8. OS2LDR, which contains a full-fledged loader for segmented executable files, has brought OS2KRNL into memory. OS2KRNL contains the critical portions of the OS/2 kernel, including the physical and virtual memory managers, the scheduler, and the file system. When control passes to OS2KRNL, the system is switched from real mode to protected mode, and the memory occupied by OS2LDR can be reused. Next, SysInit reads the CONFIG.SYS file into memory from the root directory of the boot disk. This file contains directives that control various aspects of the kernel's operation. Based on the detected hardware configuration, SysInit then loads the base set of device drivers: For a PC/AT or compatible, these are DISK01.SYS (fixed disk and floppy disk), KBD01.SYS (keyboard), SCREEN01.SYS (video display), CLOCK01.SYS (real time clock), and PRINT01.SYS (parallel port). The system file table and disk buffers are then allocated, and the file system is initialized. As under MS-DOS, the number of disk buffers is controlled by a BUFFERS= statement in CONFIG.SYS. The system file table, however, can grow dynamically; any FILES= statement in CONFIG.SYS is ignored. Until this point in the loading sequence, SysInit has been running autonomously at the highest privilege level (ring 0). SysInit brings the OS/2 kernel to life by calling a special scheduler entry point, which causes the scheduler to allocate memory for its dispatch table and then initialize it. Upon return from the scheduler, SysInit finds itself running as a normal process in user mode (ring 3). Most of the remaining SysInit tasks are carried out through documented API services. Because SysInit was not brought into memory by the system loader, which ordinarily resolves dynamic links to API entry points on behalf of processes, SysInit must use DosLoadModule and DosGetProcAddr (discussed in Chapter 19) to establish links to the API functions it requires. SysInit also uses DosLoadModule to load optional device drivers specified by DEVICE directives in the CONFIG.SYS file. A special kernel entry point provided for SysInit lets it request that the kernel run the driver's Init routine after the driver is loaded. During its Init procedure, the driver allocates any additional fixed memory it needs, initializes its adapter, and captures the necessary interrupt vectors. Similarly, SysInit uses DosExecPgm to launch any background, or "daemon," processes specified with RUN directives in the CONFIG.SYS file. Such processes are placed in a "black box" and must use special popup video calls if they need to interact with the user. Finally, SysInit loads and executes the Presentation Manager shell program named in the PROTSHELL directive in CONFIG.SYS and then calls DosExit to terminate. The memory SysInit has occupied is then reclaimed like that of any other process. The Presentation Manager shell switches the system into graphics mode and displays the desktop with the Task Manager, DOS compatibility box, and Print Spooler icons. It then inspects the root directory of the boot disk for a batch file named STARTUP.CMD. If the file exists, a copy of CMD.EXE is started in a window to carry out the commands in the file. Finally, the Program Starter menu is displayed, and the system awaits a command from the user. Figures 2-9 and 2-10 illustrate two possible configurations that result from the OS/2 boot process. The two configurations are basically the same except for the way they use lower memory. When DOS compatibility mode is enabled (Figure 2-9), the storage below the boundary specified by the RMSIZE directive in CONFIG.SYS (or 640 KB, if RMSIZE is absent) is reserved for COMMAND.COM and real mode applicationsapart from the area occupied by OS/2 "System Low" and the device drivers, of course. Protected mode programs (Figure 2-10) can still use any memory below 640 KB that is above the boundary specified by the RMSIZE directive. Ŀ Top of installed Fixed allocatable memory extended memory Ĵ Protected mode memory arena Ĵ Protected mode (Segments can applications be moved and swapped.) Ĵ System file table Ĵ Disk buffers Ĵ "System High" OS/2 kernel Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Protected mode memory arena Ĵ Protected mode (Segments can applications be moved and swapped.) Ĵ RMSIZE Transient COMMAND.COM DOS com- Ĵ patibility Ĵ Transient program area mode memory for real mode arena Ĵ Resident COMMAND.COM Ĵ Real mode device drivers Ĵ Dual mode device drivers Ĵ "System Low" OS/2 kernel Ĵ 1 KB Interrupt vectors 0 KB Figure 2-9. Final memory map for OS/2 systems supporting both protected mode and real mode applications (PROTECTONLY=NO). Depending on the RMSIZE directive in CONFIG.SYS, the memory arena for DOS compatibility mode can occupy all or only part of the memory below 640 KB that is not used by OS/2 "System Low" and its device drivers. Ŀ Top of installed Fixed allocatable memory extended memory Ĵ Protected mode memory arena Ĵ Protected mode (Segments can applications be moved and swapped.) Ĵ System file table Ĵ Disk buffers Ĵ "System High" OS/2 kernel Ĵ 1 MB ROM bootstrap ROM BIOS and ABIOS Adapter memory-mapped I/O Ĵ 640 KB Memory arena (Segments can Ĵ Protected mode be moved and applications swapped.) Ĵ Device drivers Ĵ "System Low" OS/2 kernel 0 KB Figure 2-10. Final memory map for OS/2 systems supporting protected mode applications only (PROTECTONLY=YES in CONFIG.SYS). Note that in a protected-mode-only system the interrupt vector table is not fixed at location 0 but can float anywhere in memory. Chapter 3 OS/2 Application Programs OS/2 is both a platform for a new generation of multitasking graphical applications and a bridge from the 640 KB real mode MS-DOS environment. Consequently, you can write new applications for OS/2 at several different levels of complexity: Kernel applications Family applications Presentation Manager applications Kernel applications execute only in protected mode and use the OS/2 kernel services for keyboard, screen, and mouse I/O rather than using those offered by the Presentation Manager (PM). They have full access to OS/2's multitasking, virtual memory, interprocess communication, and asynchronous I/O capabilities. Although Kernel applications can run in a window under the Presentation Manager, they are ordinarily limited to character-oriented displays. (If a program includes its own graphics drivers, it gives up the ability to run in a window.) Family applications are built like Kernel applications; however, they use only those OS/2 functions that have direct counterparts in MS-DOS, and they do not contain any machine instructions that are unique to the 80286 or 80386. A Family application goes through an extra linkage step which "binds" it to code that intercepts OS/2 API calls, if the program is loaded in real mode, and converts them to MS-DOS Int 21H calls. Such a "bound" program can run in either protected mode or real mode under OS/2 or MS-DOS on any 8086/88, 80286, or 80386-based machine. Many Microsoft programming tools are written as Family applications so that they can be used to cross-compile OS/2 programs under MS-DOS or vice versa. Presentation Manager applications have a radically different internal structure and flow of control than do Family or Kernel applications. The "main" routine is a simple loop that reads a message off an input queue, optionally translates the message, and then redispatches the message to a relatively autonomous routine, known as a window procedure, that is associated with a specific screen region. The message might consist of a keypress, a key release, a mouse movement, a signal from the system to repaint part of a window, a notification that the application has been "iconized," and so forth. Presentation Manager applications have full access to the powerful PM library of device-independent graphics services. The remainder of this chapter, and indeed most of the remainder of this book, concerns itself only with Kernel applications: their structure in source code, on disk, and in memory, and the way in which they are loaded, communicate with the operating system and other processes, obtain system resources such as files and additional memory, and terminate. OS/2 Program Source Structure An OS/2 Kernel application is built from two basic types of text files: one or more language-specific source files that can be compiled or assembled into relocatable object modules, and a module definition file that describes the program's segment characteristics. This chapter confines itself to a discussion of MASM applications, although most of the principles involved apply to high level languages as well. An OS/2 assembly language program has a well-defined structure at two levels: the level of segments and that of procedures. Segments are physical groupings of like items within a program and an enforced separation of dissimilar items; procedures are functional subdivisions of the executable program. Figure 3-1 contains a segmentation skeleton of an OS/2 assembly language program. In 80286 protected mode, a memory segment occupies from 1 to 65,536 bytes and carries one of three attributes: executable code (text), read/write data, or read-only (static) data. An executable selector cannot be used to write to its segment; code segments are sometimes described as "pure" because under normal circumstances they are not modifiable. (OS/2 does provide a way to obtain executable and data selectors that refer to the same physical memory segment; this method will be discussed in Chapter 11.) title MYPROG -- Segmentation Skeleton page 55,132 .286 ; allow 80286 instructions . ; miscellaneous equates, . ; structures, and other . ; declarations go here extrn DosExit:far ; system services used by ; program are declared here DGROUP group _DATA ; declare 'automatic data ; group' for OS/2 loader _DATA segment word public 'DATA' . ; all variable and constant . ; data goes in this segment . _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; routine which initially . ; receives control can be . ; called anything... . push 1 ; main routine terminates push 0 ; all threads and passes a call DosExit ; return code to OS/2 main endp . ; other routines needed by . ; the program go here . _TEXT ends end main ; declares end of module ; and initial entry point Figure 3-1. Skeleton of a properly segmented assembly language application for OS/2. The 80286's insistence on separation of executable code and data in protected mode dictates that every program that runs under OS/2, including assembly language programs, must have a minimum of two segments: a code segment and a data segment. The "near data" segment, or the segment that the program expects to address with offsets from the DS register, should be named as a component of DGROUP with the group directive. DGROUP is known as the "automatic data segment" and receives special treatment by the OS/2 loader. Programs are classified into one memory model or another by the number of their code and data segments. The most commonly used memory model for assembly language programs is the small model, which has one code and one data segment, but several others may also be used (Figure 3-2). For each model, Microsoft has established certain segment and class names that are used by all its high level language compilers (Figure 3-3). I recommend that you follow these conventions in assembly language programming too, because they make it easier to integrate routines from high level language (HLL) libraries into your assembly programs or to use your assembly language subroutines in HLL programs. The program must have a primary procedure that receives control from OS/2. To specify the entry point, include the procedure name in the END statement at the end of the file. The main procedure's attribute (near or far) is not particularly important because a program terminates its execution by a DosExit function call rather than by a subroutine RETurn instruction. By convention, people assign the far attribute to the main procedure. (OS/2 function calls are discussed later in this chapter.) Other procedures are assigned a near or far attribute depending on the memory model and depending on whether they are called only from the same segment or are the target of intersegment calls. Keep procedures reentrant whenever possible so that they can be shared between concurrent threads (subprocesses) without any special synchronization. If you are coding in C, procedures are almost naturally reentrant unless you declare variables static; if you are coding in assembly language, the 80286's enter and leave instructions allow the easy implementation of local, automatic variables for subroutines. Model Code Segments Data Segments Small One One Medium Multiple One Compact One Multiple Large Multiple Multiple Figure 3-2. Terminology for memory models commonly used in assembly language and C programs. Two additional models exist that are not relevant to this book: the tiny model, which consists of intermixed code and data in a single segment (for example, a COM file under MS-DOS); and the huge model, which is supported by the Microsoft C Compiler and allows use of data structures larger than 64 KB. Memory Segment Align Combine Class Model Name Type Class Name Group Small _TEXT word public CODE _DATA word public DATA DGROUP STACK para stack STACK DGROUP Medium module_TEXT word public CODE . . . _DATA word public DATA DGROUP STACK para stack STACK DGROUP Compact _TEXT word public CODE data para private FAR_DATA . . . _DATA word public DATA DGROUP STACK para stack STACK DGROUP Large module_TEXT word public CODE . . . data para private FAR_DATA . . . _DATA word public DATA DGROUP STACK para stack STACK DGROUP Figure 3-3. Segments, groups, and classes for the standard memory models as used with assembly language programs. Microsoft C programs use a superset of these segments and classes. Module Definition Files A module definition file (with the extension DEF) describes the segment characteristics of an OS/2 application program or dynamic link library. The major elements of a definition file are the following: The executable program or dynlink library name, defined with the NAME or LIBRARY directive, respectively The initial size of the local heap for C programs, defined with the HEAPSIZE directive (The heap can be expanded automatically as needed at run time, as long as the total of the program's data, stack, and local heap does not exceed 65,536 bytes.) The stack size, defined with the STACKSIZE directive The characteristics of the code and data segments, defined with the CODE and DATA directives along with suitable modifiers The names of imported (dynamically linked) or exported routines Other DEF file directives determine whether a code segment has I/O privilege (IOPL), place descriptive strings into a load module for documentation purposes, or specify the filename of an MS-DOScompatible stub program which will be merged into the new executable file and receive control if it is loaded in real mode. Module definition files are also used by the IMPLIB utility to create import librariesspecial object module libraries that contain reference records for the entry points in dynlink libraries. When the Linker resolves an extrn reference in an application against a reference record, an IMPORTS statement in the module definition file is not necessary. Thus, import libraries simplify the application building process; they are discussed further in Chapter 19. Figure 3-4 contains a summary of module definition file syntax. For more detailed information, refer to Appendix E. NAME [ module name ] [ WINDOWAPI | WINDOWCOMPAT | NOTWINDOWCOMPAT ] DESCRIPTION 'string' HEAPSIZE bytes STACKSIZE bytes PROTMODE | REALMODE EXETYPE [ OS2 | WINDOWS | DOS4 ] CODE [ PRELOAD | LOADONCALL ][ EXECUTEONLY | EXECUTEREAD ] [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ] Ŀ Ĵ If no CODE statement is present, the default attributes are LOADONCALL EXECUTEREAD NOIOPL NONCONFORMING DATA [ PRELOAD | LOADNONCALL ][ READONLY | READWRITE ] [ NONE | SINGLE | MULTIPLE ][ SHARED | NONSHARED ] [ IOPL | NOIOPL ] Ŀ Ĵ If no DATA statement is present, the attributes are LOADONCALL READWRITE MULTIPLE NONSHARED NOIOPL SEGMENTS name [ CLASS ['classname']][ PRELOAD | LOADONCALL ] [ READONLY | READWRITE ] [ EXECUTEONLY | EXECUTEREAD ][ IOPL | NOIOPL ] [ CONFORMING | NONCONFORMING ] [ SHARED | NONSHARED ] . Ŀ . Number of stack parameters (words); Ĵ required only for IOPL segments EXPORTS Ŀ exportname [ stackparams ] . . IMPORTS [ internalname = ] modulename.entryname | modulename.ordinal . . STUB 'filename.EXE' Figure 3-4. Syntax summary of a module definition file for OS/2 applications. Defaults are in boldface. Keywords must always be entered in uppercase letters. A similar syntax summary for drivers and dynlink libraries appears in Chapter 19. OS/2 Program Files Source code files are first translated to relocatable object modules, which contain machine code and symbolic information, by an assembler or compiler. Object modules are not suitable for execution without further processing. For easier management they are often collected together into object module libraries. Object modules are converted into executable programs (with the aid of module definition files, import libraries, and object module libraries) by a utility called the Segmented Executable Linker. The executable programs then reside on the disk as load modules called "segmented executable" files, or "new EXE" files. The format of a segmented executable file is identical to that of the load modules used by Microsoft Windows and is a superset of the "old EXE" format of MS-DOS. It has the following components: A header describing the program's structure A "stub" program for real mode At least one code segment At least one data segment The header contains, among other elements, a "signature" (identifying the type of load module), a checksum, the program entry point, information about the code and data segments in the file, and a list of external routines to be dynamically linked at load time (also called imported routines). The stub program is executed if the program is invoked in real mode (either in DOS compatibility mode or under MS-DOS). The default stub program is a short routine inserted by the Linker that displays the message This program cannot run in DOS mode and terminates. Each segment in the file has an attribute (code, read-only data, or read/write data), its own relocation table, and flags indicating whether it should be preloaded or loaded on demand. Code segments are always read-only ("pure") and can be shared among multiple instances of the same program. Theoretically, the size of an OS/2 program is limited only by the number of symbols and segments that the Linker can process. Practically speaking, program size is limited by the amount of disk space available to hold the executable file, the virtual memory manager's swap file, and the number of available LDT selectorsthis still allows for some pretty large programs! A block diagram of a segmented EXE file can be found in Figure 3-5. See Appendix D for a more detailed description of the file format. Ŀ 00H MS-DOS-compatible ("old EXE") header Ĵ 20H Reserved Ĵ 3CH Offset to "new EXE" header Ĵ 40H MS-DOS stub program and relocation table Ĵ New EXE header Ĵ Segment table Ĵ Resource table Ĵ Resident names table Ĵ Module reference table Ĵ Imported names table Ĵ Entry table Ĵ Nonresident names table Ĵ Segment #1 program code or data Segment #1 relocation information Ĵ Ĵ Segment #n program code or data Segment #n relocation information Figure 3-5. Block diagram of the "new EXE" format for executable program files under OS/2. In Presentation Manager applications, "resources" (such as icons, bitmaps, string tables, and menus) follow the last code or data segment. Program Loading A general-purpose mechanism called the system loader brings programs into memory and prepares them for execution. Command processors and other programs invoke the loader by calling the OS/2 function DosExecPgm (described in more detail in Chapter 12). When the loader is called upon to load a program, it inspects the EXE file header and allocates sufficient memory to hold the segments in the program that are marked "preload." These typically include at least one segment holding machine code (text), a segment for the program's data and stack, and a segment for the environment, program name, and command line information (Figure 3-6 on the following page). Ŀ Command line information Ĵ AX:BX Program name Ĵ Environment block AX:0000H Ŀ Ŀ Local heap (if C program) Ĵ SS:SP Stack DGROUP  Ĵ Near data segment (_DATA) SS=DS:0000H Ŀ Program code (_TEXT) CS:0000H Figure 3-6. Memory image of a typical "small model" MASM or C program immediately after loading. The program's data, stack, and local heap reside in the same physical segment, called DGROUP. The segments need not be arranged in the order shown here; in fact, the physical location is not visible to the program itself. The entry point can be anywhere in the code segment and is specified by an end statement in one of the source modules of the program. The loader also performs a memory overcommit calculation to make sure that enough physical memory and swap file space will be available at any point during the program's execution. The loader's attempts to allocate memory for a new program might cause the memory manager to move, swap, or discard segments belonging to other programs. As each segment is read into memory from the EXE file, the loader performs any necessary selector fixups as specified by the segment's associated relocation table. Segments that are marked "load on demand" are assigned a selector but are not initially brought into physical memory. The loader also performs any loadtime dynamic linking that may be required, fixing up the addresses in far calls to OS/2 system services or to application-specific dynlink libraries. Once the essential parts of a program have been loaded into memory and relocated, any dynamic links have been resolved, and the program has been added to the list of jobs maintained by the kernel's scheduler, the program becomes a process. Program Entry Conditions The initial values of the code segment (CS) and instruction pointer (IP) registers are calculated from the entry point information in the EXE file header. The data segment (DS) and stack segment (SS) registers are initialized with the segment selector for DGROUP, which contains the program's near data, stack, and (for C programs) local heap. The stack pointer (SP) is loaded with the offset of the base of the program's stack, while the AX and BX registers are set up with a selector and offset that provide access to the program's environment block, filename, and command line. Other registers contain less vital information, such as the stack size and heap size (Figure 3-7). Register Contents CS:IP Initial entry point SS:SP Base of default stack DS Segment selector for DGROUP ES Zero AX Segment selector of environment BX Offset of command line information following environment CX Initial size of DGROUP (0 = 65,536 bytes) DX Initial size of default stack SI Initial size of local heap (C programs) DI Module handle for the process BP Zero Figure 3-7. Register contents at entry to a protected mode OS/2 application. The environment block for a particular process is inherited from the process's parent. It consists of a series of null-terminated ASCII (ASCIIZ) strings, called environment variables, of the following form: NAME=PARAMETER Variables are placed in the environment block as a result of certain directives in the CONFIG.SYS file, and by the command processor as a result of PROMPT, PATH, and SET commands (Figure 3-8 on the following page). When multiple sessions are active, the command processor in each session maintains a "master environment" for that session. COMSPEC=C:\OS2\PBIN\CMD.EXE PROMPT=$p$_PM$g PATH=C:\;C:\OS2\BIN;C:\OS2\PBIN;C:\OS2TOOLS INCLUDE=c:\os2tools\include LIB=c:\os2tools\lib TMP=c:\temp TEMP=c:\temp INIT=c:\init USER=c:\init Figure 3-8. Typical environment block for protected mode session (displayed with the command SET). When multiple sessions are active, the command processor in each session maintains a "master environment" for that session. Environment variables provide information to command processors and application programs; they do not affect the operation of the OS/2 kernel. For example, the Microsoft C Compiler looks in the environment for INCLUDE, LIB, and TMP variables to tell it where to find header (.H) files and object module libraries and where to put temporary working files. The last null-terminated string in the environment block is followed by an additional null byte, which signifies the end of the block. Immediately following that byte, OS/2 places the fully qualified name of the file from which the program was loaded, including the logical drive identifier and full path from the root of that drive. This ASCIIZ string corresponds to argv[0] in C programs; the program can use this information to create another instance of itself or to find its "home directory" in order to load overlays or configuration files. The program filename is followed by the command line information, in the form of a set of ASCIIZ strings called the "argument strings." When a program is loaded by CMD.EXE, the first argument string is the simple name of the program and the second is the command tail (the command line excluding the program name). The argument strings are terminated by an additional null byte, in the same fashion as the environment block. Redirection or piping parameters do not appear in the argument strings passed by CMD.EXE because redirection is transparent to applications. Figure 3-9 contains a hex and ASCII dump of a typical program's environment block, filename, and argument strings. To produce the dump in the figure, the OS/2 FIND utility was run with the following command line: C>find /n "extrn" start.asm 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 005F:0000 33 58 42 4F 58 3D 63 3A 5C 6F 73 32 73 79 73 5C 3XBOX=c:\os2sys\ 005F:0010 63 6F 6D 6D 61 6E 64 2E 63 6F 6D 00 43 4F 4D 53 command.com.COMS 005F:0020 50 45 43 3D 43 3A 5C 4F 53 32 53 59 53 5C 43 4D PEC=C:\OS2SYS\CM 005F:0030 44 2E 45 58 45 00 50 41 54 48 3D 43 3A 5C 3B 43 D.EXE.PATH=C:\;C 005F:0040 3A 5C 4F 53 32 53 59 53 3B 43 3A 5C 54 4F 4F 4C :\OS2SYS;C:\TOOL 005F:0050 53 00 44 50 41 54 48 3D 43 3A 5C 3B 43 3A 5C 4F S.DPATH=C:\;C:\O 005F:0060 53 32 53 59 53 3B 43 3A 5C 54 4F 4F 4C 53 00 49 S2SYS;C:\TOOLS.I 005F:0070 4E 43 4C 55 44 45 3D 63 3A 5C 69 6E 63 6C 75 64 NCLUDE=c:\includ 005F:0080 65 00 4C 49 42 3D 63 3A 5C 6C 69 62 00 49 4E 49 e.LIB=c:\lib.INI 005F:0090 54 3D 63 3A 5C 69 6E 69 74 00 54 4D 50 3D 63 3A T=c:\init.TMP=c: 005F:00A0 5C 74 65 6D 70 00 54 45 4D 50 3D 63 3A 5C 74 65 \temp.TEMP=c:\te 005F:00B0 6D 70 00 50 52 4F 4D 50 54 3D 24 70 24 5F 50 4D mp.PROMPT=$p$_PM 005F:00C0 24 67 00 00 43 3A 5C 50 4D 46 5C 46 49 4E 44 2E $g..C:\PMF\FIND. 005F:00D0 45 58 45 00 66 69 6E 64 00 20 2F 6E 20 22 65 78 EXE.find. /n "ex 005F:00E0 74 72 6E 22 20 73 74 61 72 74 2E 61 73 6D 00 00 trn" start.asm.. Figure 3-9. Sample hex dump of the information that is passed to an OS/2 program at entry. The two zero bytes at offset 00C2H mark the end of the environment and are followed by the program filename. The two argument strings appear at offsets 00D4H and 00D9H. Process Execution and the API While a process is executing, it communicates with the operating system via the Application Program Interface (API). The API functions allow a program to request and release additional system resources, start and communicate with other processes, and perform input and output in a hardware-independent fashion. Some API services also allow a process to obtain additional information about its environment and about the system state and hardware capabilities (Figure 3-10 on the following page). A subset of the API functions, called the Family API (FAPI), corresponds directly to the functions supported by MS-DOS. Each API function has a named entry point, is declared with an extrn far statement, and is entered by a far call. Parameters for API functions are pushed onto the stack prior to the call. A parameter can be a value, or it can be an address of a value or variable (commonly a structure). For kernel API functions, a status code is returned to the program in register AX. This code is zero if the function succeeded or an error code if the function failed. Other returned values are placed in variables whose addresses were passed on the stack in the original call. (Presentation Manager API functions use other conventions.) Function Description DosDevConfig Gets information about system configuration: number of floppy disks, type of monitor, presence/absence of 80287 or 80387, PC model type, number of printers and serial ports. DosGetCtryInfo Gets internationalization information such as the country code, date and time format, and currency symbol. DosGetDateTime Gets the current date, time, and day of the week. DosGetEnv Gets a pointer to process's environment block, program filename, and command line. DosGetInfoSeg Gets pointers to read-only global and local information segments containing the process ID, screen group number, current date, time, length of timeslice, and other useful information. DosGetMachineMode Returns a flag indicating whether the process is running in real mode or protected mode. DosGetPID Returns the current process ID, thread ID, and parent's process ID. DosGetPrty Gets the execution priority of a thread. DosGetVersion Gets the OS/2 version number. DosMemAvail Returns the size of the largest free block in physical memory. DosQCurDir Gets the pathname to the current directory. DosQCurDisk Gets the identifier for the current disk drive. DosQFSInfo Returns information about the file system on the specified device, such as volume label, bytes per sector, total number of allocation units, and number of free allocation units. DosQSysInfo Returns system information that does not change after the system is booted. DosQVerify Gets the current setting of the system verify (read after write) flag for disk operations. DosScanEnv Searches the environment block for an environment variable. VioGetConfig Returns information about the type of video display and adapter, and the amount of memory installed on the adapter. VioGetMode Returns information about the current video display mode, including the number of lines, columns, and displayable colors. VioGetState Returns information about the video controller state: palette, intensity/blink toggle, and border color. Figure 3-10. API functions which can be used by a program to obtain information about its execution environment and the system state and capabilities. These functions are described again in more detail at appropriate locations elsewhere in this book. For example, the kernel function DosWrite takes as its parameters the handle for a file or device, the address and length of the data to be written, and the address of a variable in which it will return the actual number of bytes written. extrn DosWrite:far stdout equ 1 ; standard output handle wlen dw ? ; receives length written msg db 'Hello World!' ; message to be written msg_len equ $-msg ; length of message . . . push stdout ; standard output handle push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; was write successful? jnz error ; jump if function failed . . . Calling the OS/2 API is equally efficient from a high level language or from assembly language, because high level languages typically use the stack for parameter passing, just as the OS/2 API does. For example, to call an OS/2 API function from a C program, you simply declare it as far pascal indicating that the parameters are pushed left to right and that the called routine clears the stackand then invoke it directly by name: unsigned extern far pascal DosWrite(unsigned, char far *, unsigned, unsigned far *); . . . unsigned status, wlen; char *msg = "Hello World!"; status = DosWrite(1, msg, strlen(msg), &wlen); . . . The C Compiler generates the correct code for the OS/2 call automatically. It introduces no execution time or space penalty, and it requires no intermediate library functions to shift parameters around or pop them into registers before transferring to the operating system. The OS/2 API services can be called directly in a similar fashion from Pascal, FORTRAN, or even Forth programs. Process Termination When an application program has finished processing, it terminates itself and passes an "exit" code back to its parent with the API function DosExit. A process can also be terminated by an external event, such as a call to DosKillProcess by its parent process, a general protection (GP) fault, the user's entry of Ctrl-C or Ctrl-Break, or the user's response to an operating system pop-up that reports a critical error. Upon termination of a process, regardless of the cause, OS/2 discontinues any pending I/O, closes any open files, and releases all memory and other system resources owned by the process. OS/2 then notifies the process's parent of the exit code and manner of termination, if the parent requested that information. A process can intercept or disable some external events to prevent its termination or to allow an orderly cleanup before termination. For these purposes, the API includes the functions DosError, DosSetSigHandler, DosSetVec, and DosExitList. A process cannot, however, prevent termination resulting from a GP fault. A Sample OS/2 Application Let's look at a traditional, trivial program, written in Microsoft Macro Assembler, that displays the message Hello World! on the standard output device (which defaults to the video display). This sample program is created from two files: HELLO.ASM (the assembly language source file) and HELLO.DEF (the module definition file). The Source File HELLO.ASM The file HELLO.ASM contains the assembly language source code for our sample program (Figure 3-11). It demonstrates the fundamental structure of a small model assembly language program that is destined to become an OS/2 EXE file. Because this program is so short and simple, a relatively high proportion of the source code consists of assembler directives that do not result in any executable code. The title directive, on line 1, specifies the text string (limited to 60 characters) to be printed on the upper left corner of each page. The title directive is optional, but it cannot be used more than once in each assembly language source file. 1: title HELLO -- Display Message on stdout 2: page 55,132 3: .286 4: 5: ; 6: ; HELLO.EXE 7: ; 8: ; A simple OS/2 assembly language program. 9: ; 10: ; Copyright (C) 1986 Ray Duncan 11: ; 12: 13: stdin equ 0 ; standard input handle 14: stdout equ 1 ; standard output handle 15: stderr equ 2 ; standard error handle 16: 17: extrn DosWrite:far 18: extrn DosExit:far 19: 20: DGROUP group _DATA 21: 22: 23: _DATA segment word public 'DATA' 24: 25: msg db 0dh,0ah,"Hello World!",0dh,0ah 26: msg_len equ $-msg 27: 28: wlen dw ? ; receives bytes written 29: 30: _DATA ends 31: 32: 33: _TEXT segment word public 'CODE' 34: 35: assume cs:_TEXT,ds:DGROUP 36: 37: print proc far 38: 39: push stdout ; standard output handle 40: push ds ; address of data 41: push offset DGROUP:msg 42: push msg_len ; length of data 43: push ds ; receives bytes written 44: push offset DGROUP:wlen 45: call DosWrite ; transfer to OS/2 46: or ax,ax ; was write successful? 47: jnz error ; jump if function failed 48: 49: push 1 ; terminate all threads 50: push 0 ; return success code 51: call DosExit ; transfer to OS/2 52: 53: error: push 1 ; terminate all threads 54: push 1 ; return error code 55: call DosExit ; transfer to OS/2 56: 57: print endp 58: 59: _TEXT ends 60: 61: end print Figure 3-11. Source file HELLO.ASM for the sample application HELLO.EXE. Line numbers at left are for reference in the text and are not part of the actual source file. The page directive, when used with two operands as in line 2, defines the length and width of the printer page. These default to 66 lines long and 80 characters wide. If you use a page directive within your listing without any operands, it causes a form feed to be sent to the printer and a heading printed. In larger programs, use the page directive liberally to place each of your procedures on a separate sheet for easy reading. The .286 directive (line 3) enables the assembly of 80286 nonprivileged instructions that are not present in the 8086 instruction set. The most handy of these is the push immediate instruction, which saves time and space when setting up parameters for an OS/2 API call. Lines 1315 equate symbols to the predefined handles for the standard input, standard output, and standard error devices. Although we wouldn't expect these values ever to change, assigning them meaningful names makes the program more readable. References to OS/2 API entry points are accomplished with extrn directives, which assign a far attribute to the external name (lines 17 and 18). The assembler, of course, does not know the nature of the procedure represented by the external name, only that it has to generate a far call to reach it and that the final address will be fixed up later. The declaration of DGROUP with the group directive (line 20) is mandatory. DGROUP is a "magic" name that specifies the application's automatic data segment, which also contains the default stack. Unlike MS-DOS, OS/2 automatically initializes the DS register to point to DGROUP before it transfers control to the program's entry point. (The other conditions at entry to a protected mode application were discussed earlier in this chapter and are summarized in Figure 3-7 on page 37.) Now let's examine lines 23 through 30, where we declare a data segment that contains the variables and constants used by our program. The data segment is initiated with the segment directive in line 23; it is given the name _DATA by the label field of the statement and acquires certain attributes (word, public, and the classname DATA) from the operand field. The data segment is terminated with an ends directive in line 30. The Linker knows, by the class name and the association of segment name _DATA with the group name DGROUP, that this segment of the program does not contain executable machine code. Next we come to the declaration of a machine code segment that begins in line 33 with a segment directive and terminates line 59 with the ends directive. The segment instruction assigns the name _TEXT and the attributes word, public, and the classname CODE to the segment. You will find it quite helpful to read the Microsoft Macro Assembler Programmer's Guide for detailed explanations of each possible attribute of a segment. The _TEXT and _DATA segment names are simply conventions which are used by the Microsoft high level language compilers; these conventions are explained in great detail in the MASM and Microsoft C Compiler manuals. Following the segment instruction, we encounter an assume directive in line 35. In this statement we assert that the CS and DS segment registers have been loaded, at this point of execution of the final program, with the selectors for the _TEXT segment and DGROUP, respectively. The assume directive often baffles new assembly language programmers. It doesn't appear to do anythingit generates no codeit only notifies the assembler which segment registers point to which memory segments, so that the assembler can generate segment override prefix bytes when they are needed. The responsibility for actually loading the segment registers remains with the programmer and the operating system. Within the _TEXT segment, we come to another type of block declaration, begun with the proc directive in line 37 and closed with endp in line 57. These two instructions declare the beginning and end of a procedure, or block of executable code that performs a single distinct function. The procedure is given a name by the label in the leftmost field of the proc statement, and an attribute in the operand fieldin this case, the name of the procedure is print. If the procedure carries the near attribute, it can be called only by other code in the same segment, whereas procedures with the far attribute can be called from program code located anywhere in the process's address space. Calls to two different OS/2 services, DosWrite and DosExit, are demonstrated in lines 39 through 55. DosWrite, which we encountered earlier in this chapter, performs a synchronous write to a file or device ("synchronous" in that the write request is logically completed before control returns to the requesting process); it is analogous to MS-DOS's Int 21H Function 40H. The returned status of the operation is tested in lines 4647; the status is zero if the operation succeeded. DosExit terminates a process, passing a return code back to its parent process; it is comparable to MS-DOS's Int 21H Function 4CH. Our program contains two calls to DosExit: lines 4951, which pass a return code of zero (signifying that our program ran successfully), and lines 5355, which pass a nonzero return code (indicating that an error condition caused the program to terminate). The latter call executes if DosWrite returns an error status. The end directive in line 61 winds up our little sample program; it tells the Macro Assembler that it has reached the end of the source file and designates the initial entry point when the executable program is loaded by OS/2. Note that we did not declare a segment with attribute STACK in this source file, as we would have done under MS-DOS. In OS/2 programs, the stack is conveniently declared in the module definition file instead. You can, however, still declare it the old-fashioned way within the ASM file with a segment...ends sequence or with the special MASM directive .STACK. The Module Definition File HELLO.DEF The file HELLO.DEF (Figure 3-12) is the module definition file for our program. It demonstrates only a few of the possible directives and options that can be used in such a file. The NAME directive states that we are building an executable program rather than a dynamic link library (whose DEF file would contain LIBRARY instead). PROTMODE signifies that the program will run in protected mode. NAME HELLO WINDOWCOMPAT ; module name PROTMODE ; protected mode only DATA PRELOAD READWRITE CODE PRELOAD STACKSIZE 4096 Figure 3-12. The module definition file HELLO.DEF for the sample application program HELLO.EXE. Note that the stack size is declared here rather than in the HELLO.ASM file. The lines beginning with DATA and CODE declare a few of the many possible segment attributes for the segments that use the Microsoft naming conventions _DATA and _TEXT. Segments in the program that used other names would be assigned attributes with the SEGMENTS directive. The stack size for the program's initial point of execution is defined by the STACKSIZE directive; PUSH and POP instructions will access this area of scratch memory. This stack is placed in DGROUP above the program's data and is referenced using the same selector (that is, DS=SS). Microsoft recommends that at least 2 KB of stack space be available at the time of any kernel API call, in addition to the stack space used by the program. Stacks for additional threads must be allocated by the program at run time (as is explained further in Chapter 12). Chapter 4 OS/2 Programming Tools Writing an OS/2 Kernel application is an iterative cycle with four basic steps: 1. Use a text editor to create or modify an ASCII source code file and module definition (DEF) file. 2. Use an assembler or high level language compiler to translate the source file into relocatable object code. 3. Use a linker to transform the relocatable object code into an executable OS/2 load module. 4. Use a debugger to methodically test and debug the program. An OS/2 software developer might find the following additional utilities necessary or helpful: LIB, which creates and maintains object module libraries IMPLIB, which creates import reference libraries CREF, which generates a cross-reference listing MAKE, which compares dates of files and carries out operations based on the result of the comparison BIND, which converts a protected mode executable file into one that can be used in either protected mode or real mode MARKEXE, which marks an application's header so that it can run in a window under Presentation Manager RC, which compiles Presentation Manager resources such as icons, bitmaps, and menus and merges them into an application CodeView, Microsoft's symbolic debugger This chapter is an operational overview of the Microsoft (and IBM) tools for programming OS/2 Kernel applications, including the Macro Assembler, C Compiler, Linker, Library Manager, MAKE, CREF, and BIND. The CodeView debugger, the MARKEXE utility, and the tools specific to the Presentation Manager (such as RC) are not discussed here, and IMPLIB is covered in Chapter 19. Even if your preferred programming language is not C or assembler, you will need at least a passing familiarity with the tools described in this chapter because all the examples in the IBM and Microsoft OS/2 reference manuals are written in either C or assembler. The survey in this chapter, together with the sample programs and reference section elsewhere in this book, is intended to provide the experienced programmer with sufficient information to begin writing useful programs. If you lack a background in C, assembly language, or the 80286/386 architecture, refer to the tutorial and reference works listed in Appendix C. File Types The OS/2 programming tools can create and process files of many types. The extensions conventionally used for these files appear in Figure 4-1. Extension File Type ASM Assembly language source file C C program source file CRF Cross-reference information file, produced by the assembler for processing by CREF DEF Module definition file, which specifies the characteristics of each program segment, the size of the heap and stack, and any imported and exported functions for dynamic linking DLL An OS/2 dynamic link library EXE An OS/2 segmented executable load module; requires additional relocation and dynamic linking at run time H C "header" file; contains C source code for constants, macros, and functions and is merged into another C program with #include INC "Include" file for assembly language programs; typically contains macros and/or equates for systemwide values such as error codes LIB Object module library file comprising one or more OBJ files, indexed and manipulated by the LIB utility LST Program listing produced by the assembler; includes machine code, memory locations, original program text, and any error messages MAP Listing of symbols and their locations within a load module, produced by the Linker OBJ Relocatable object code file produced by an assembler or compiler RC Resource Compiler source file; specifies menus, dialog boxes, strings, icons, cursors, and bitmaps for inclusion in a PM application REF Cross-reference listing produced by CREF from the information in a CRF file RES Intermediate file produced by Resource Compiler from an RC file; merged into an EXE file to build a PM application SYS An OS/2 device driver Figure 4-1. Conventionally assigned extensions associated with various file types under OS/2. Using the Macro Assembler The Microsoft Macro Assembler is distributed as the file MASM.EXE. When it begins a program translation, the Macro Assembler needs the following information: the name of the file containing the source program, the filename for the object program to be created, the destination of the program listing, and the filename for the information that is later processed by the cross-reference utility (CREF). You can invoke the assembler in two ways. If you enter the name of the assembler alone, you will be prompted for the names of each of the various input and output files. The assembler supplies reasonable defaults for all responses except the source filename. For example: [C:\] MASM Microsoft (R) Macro Assembler Version 5.10 Copyright (C) Microsoft Corp 1981, 1988. All rights reserved. Source filename [.ASM]: HELLO Object filename [HELLO.OBJ]: Source listing [NUL.LST]: Cross-reference [NUL.CRF]: 48982 Bytes symbol space free 0 Warning Errors 0 Severe Errors [C:\] You can use a logical device name (such as PRN or COM1) at any of the assembler prompts to send any of the outputs of the assembler to a character device rather than to a file. Note that the default destination for the listing and cross-reference files is the NUL device; that is, no file is created. If you end any response with a semicolon, the remaining responses are assumed to be the default. A more efficient way to use the assembler is to supply all parameters on the command line: MASM [options] source,[object],[listing],[crossref] For example, the following command lines are equivalent to the interactive session above: [C:\] MASM HELLO,,NUL,NUL or [C:\] MASM HELLO; These commands use the file HELLO.ASM as source, generate the object code file HELLO.OBJ, and send the listing and cross-reference files to the bit bucket. The Macro Assembler accepts a number of optional parameters or switches on the command line that control code generation and output files. The switches accepted by MASM versions 5.1 and later are listed in Figure 4-2. In other versions of the Microsoft Macro Assembler, additional or fewer switches might be available. For exact instructions, see the manual that corresponds to the version of the assembler you are using. Switch Meaning /A Arranges segments in alphabetic order /Bn Sets size of source file buffer (in KB) /C Forces creation of a cross-reference (CRF) file /D Produces listing on both passes (to find phase errors) /Dsymbol Defines symbol as a null text string (symbol can be referenced by conditional assembly directives in file) /E Assembles for 80x87 numeric coprocessor emulator using IEEE real number format /H Displays the MASM command line syntax and available options /Ipath Sets search path for include files /L Forces creation of a program listing file /LA Forces listing of all generated code /ML Preserves case sensitivity in all names (uppercase names are distinct from their lowercase equivalents) /MU Converts all lowercase names to uppercase /MX Preserves lowercase in external names only (names defined with public or extrn directives) /N Suppresses generation of tables of macros, structures, records, segments, groups, and symbols at the end of the listing /P Checks for impure code in 80286/386 protected mode /S Arranges segments in order of occurrence (default) /T "Terse" mode, suppresses all messages unless errors are encountered during the assembly /V "Verbose" mode, reports number of lines and symbols at end of assembly /Wn Sets error display ("warning") level, n = 0, 1, or 2 /X Forces listing of false conditionals /Z Displays source lines containing errors on the screen /Zd Includes line number information in OBJ file /Zi Includes line number and symbol information in OBJ file Figure 4-2. Summary of Microsoft Macro Assembler version 5.1 switches and options. The assembler allows you to override the default extensions on any filea "feature" that can be rather dangerous. For example, if in the previous example you had responded to the Object filename prompt with HELLO.ASM, the assembler would have accepted the entry without comment and overwritten your source file. You are unlikely to make this mistake in the interactive command mode, but you must be very careful with file extensions when MASM is run from a batch or "make'' file. Using the C Compiler The Microsoft C Compiler consists of three executable files (C1.EXE, C2.EXE, and C3.EXE) that implement the C preprocessor, language translator, code generator, and code optimizer. An additional control program, CL.EXE, is provided to execute the three compiler files in order (passing each the necessary information about filenames and compilation options) and to execute the Microsoft Segmented Executable Linker (LINK.EXE). Before you use the C Compiler and the Linker, set up four environment variables: PATH=path Used by CL.EXE to find the three executable C compiler files (C1, C2, and C3) if they are not found in the current directory INCLUDE=path Specifies the location of include files that do not reside in the current directory LIB=path Specifies the location(s) for object code libraries that do not reside in the current directory TMP=path Specifies the location for temporary working files created by the C Compiler and Linker CL.EXE does not support an interactive mode or response files. You always invoke it with a command line of the form: CL [options] file [file ...] You can list any number of files; if a file has a C extension, it is compiled into a relocatable object (OBJ) module file. Ordinarily, if no errors are encountered during compilation, all resulting OBJ files and any additional OBJ files specified on the command line are automatically passed to the Linker along with the names of the appropriate runtime libraries. The C Compiler has many options for controlling its memory models, output files, and code generation and optimization. These options are summarized in Figure 4-3. Unlike the options available in MASM, the options available in the C Compiler are case-sensitive. The arcane switch syntax is largely derived from UNIX, so don't expect it to make any sense. Switch Meaning /Ax Selects memory model d = assume DS = SS C = compact model H = huge model L = large model M = medium model S = small model (default) u = assume DS != SS; DS reloaded on function entry w = assume DS != SS; DS not reloaded on function entry /c Compiles only, does not invoke Linker /C Does not strip comments /D[=text] Defines macro or constant used in source file /E Sends preprocessor output to standard output /EP Same as /E, but no line numbers /F Sets stack size (in hex bytes) /Fa[filename] Generates assembly listing /Fb[filename] Invokes BIND and LINK to create dual mode executable /Fc[filename] Generates mixed source/object listing /Fe[filename] Forces executable filename /Fl[filename] Generates object listing file /Fm[filename] Generates map file /Fo[filename] Forces object module filename /FPx Floating point control a = calls with alternate math library c = calls with emulator library c87 = calls with 8087 library i = inline with emulator (default) i87 = inline with 8087 /Fs[filename] Generates source listing /Gx Selects code generation 0 = 8086 instructions (default) 1 = 186 instructions 2 = 286 instructions c = Pascal style function calls s = no stack checking t[n] = data size threshold /H Specifies external name length /HELP Displays the C Compiler command line syntax and available options /I Specifies additional #include path /J Sets default char type to unsigned /Lc Links for execution under MS-DOS /link [options] Passes switches and library names to Linker /Lp Links for execution under OS/2 /Lr Same as /Lc /ND name Assigns name to data segment /NM name Assigns name to module /NT name Assigns name to code (text) segment /Ox Selects optimization a = ignore aliasing d = disable optimizations i = enable intrinsic functions l = enable loop optimizations n = disable "unsafe" optimizations p = enable precision optimizations r = disable in-line return s = optimize for space t = optimize for speed (default) w = ignore aliasing except across function calls x = maximum optimization (/Oailt /Gs) /P Sends preprocessor output to file /Sx Source listing control l = set line width p = set page length s = set subtitle string t = set title string /Tc Compiles file without C extension /u Removes all predefined macros /U Removes specified predefined macro /V Sets version string /W Sets warning level (03) /X Ignores the INCLUDE environment variable when searching for include files /Zx Miscellaneous compilation control a = disable extensions c = make Pascal functions case-insensitive d = line number information e = enable extensions (default) g = generate declarations i = symbolic debugging information l = remove default library information p = pack structures on n-byte boundary s = syntax check only Figure 4-3. Summary of Microsoft C version 5.1 compiler switches and options. Using the Linker The object module produced by an assembler or compiler from a source file is in a form that contains relocation information. It can also contain unresolved references to external locations or routines. The Linker accepts one or more object modules, resolves external references, includes any necessary routines from designated libraries, performs any offset relocations that might be necessary, and writes a file that can be loaded and executed by the OS/2 operating system. As with the Macro Assembler, you can supply parameters to the Linker interactively or enter all the required information on a single command line. If you simply enter the utility name alone, the following dialog ensues. [C:\] LINK Microsoft (R) Segmented-Executable Linker Version 5.01.21 Copyright (C) Microsoft Corp 1984-1988. All rights reserved. Object Modules [.OBJ]: HELLO Run File [HELLO.EXE]: List File [NUL.MAP]: HELLO Libraries [.LIB]: OS2 Definitions File [NUL.DEF]: HELLO [C:\] The input files for this run of the Linker are the object module HELLO.OBJ, the module definition file HELLO.DEF, and the import library OS2.LIB (which contains dynlink reference records for the OS/2 kernel API). The output files are HELLO.EXE (the executable program) and HELLO.MAP (the load map produced by the Linker after all references and addresses are resolved, Figure 4-4). HELLO Start Length Name Class 0001:0000 00012H _DATA DATA 0002:0000 00027H _TEXT CODE Origin Group 0001:0 DGROUP Program entry point at 0002:0000 Figure 4-4. Map produced by Linker during the generation of the HELLO.EXE program, Figure 3-11 in Chapter 3. The program contains one CODE segment and one DATA segment. The STACK size was set by an entry in the HELLO.DEF file and is therefore not shown as a segment. The first instruction to be executed lies in the first byte of the CODE segment. This simple program declares only one group: the "automatic data group" DGROUP, which must be present in all OS/2 programs. You can obtain the same result more quickly by entering all parameters on the command line: LINK [options] objectfiles, [exefile], [mapfile], [libraries], [deffile] Thus, the command line equivalent to the session above is as follows: [C:\] LINK HELLO,,HELLO,OS2,HELLO The entry of a semicolon as the last character of the command line causes the Linker to assume the default values for all further parameters. This is not normally a useful feature in OS/2 development, however, because dynamic link reference libraries and module definition files are almost always required. A third method of commanding the Linker is to use a response file. A response file contains lines of text that correspond to the responses you would give the Linker interactively. The name of the response file is specified on the command line with a leading @ character: LINK @filename The name of a response file can also be entered at any prompt. If the response file is not complete, a prompt is issued for the missing information. When entering Linker commands, you can specify multiple object files with the + operator or with spaces, as in the following: [C:\] LINK HELLO+VMODE+DOSINT,MYPROG,,OS2,HELLO which links the files HELLO.OBJ, VMODE.OBJ, and DOSINT.OBJ, using the import library OS2.LIB, and produces a file named MYPROG.EXE. LINK uses the current drive and directory if you do not explicitly specify otherwise in a filename. It does not automatically use the same drive and directory you specified for a previous file on the same command line. If multiple library files are to be searched, enter them in the libraries field separated by + or space characters. You can specify a maximum of 32 libraries. The default libraries provided with each of the high level language compilers are searched during the linkage process (if the Linker can find them), unless you explicitly exclude them with the /NOD option. The Linker first looks for libraries in the current directory of the default disk drive, then along any paths that are provided in the command line, and finally along the path or paths specified by the LIB variable (if it is present in the environment). The Linker accepts a number of optional parameters as part of the command line or at the end of any interactive prompt. These switches are listed in Figure 4-5. The number of options available varies between different versions of the Microsoft Linker. See your Linker reference manual for detailed information about your particular version. Switch Meaning /A:n Sets segment sector alignment factor. N must be a power of 2 (default = 512). Not related to logical segment alignment (byte, word, para, page, etc.). Relevant to segmented executable files (Microsoft Windows and OS/2) only. /B Suppresses Linker prompt if a library cannot be found in the current directory or in the locations specified by the LIB environment variable. /CO Includes symbolic debugging information in the EXE file for use by CodeView. /CP Sets the field in the EXE file header controlling the amount of memory allocated to the program in addition to the memory required for the program's code, stack, and initialized data. Relevant to real mode programs only. /DO Uses standard Microsoft segment naming and ordering conventions. /DS Loads data at the high end of the data segment. Relevant to real mode programs only. /E Packs executable file by removing sequences of repeated bytes and optimizing relocation table. /F Optimizes far calls to labels that are within the same physical segment for speed by replacing them with near calls and NOPs. /HE Displays information about available options. /HI Loads the program as high in memory as possible. Relevant to real mode programs only. /I Displays information about link progress, including pass numbers and the names of object files being linked. /INC Forces production of SYM and ILK files for subsequent use by ILINK (incremental Linker). Cannot be used with /EXEPACK. /LI Writes the address of the first instruction that corresponds to each source code line to the map file. Has no effect if compiler does not include line number information in the object module. Forces creation of a map file. /M[:n] Forces creation of a MAP file listing all public symbols sorted by name and by location. The optional value n is the maximum number of symbols which may be sorted (default = 2048); when n is supplied, the alphabetically sorted list is omitted. /NOD Skips search of any default compiler libraries that are specified in OBJ file. /NOE Ignores the extended library dictionary (if it is present). The extended dictionary ordinarily provides the linker with information about inter-module dependencies to speed up linking. /NOF Disables optimization of far calls to labels within the same segment. /NOG Causes the Linker to ignore group associations when assigning addresses to data and code items. Revelant for real mode programs only. /NOI Does not ignore case in names during LINK. /NON Arranges segments as for /DO but does not insert 16 null bytes at start of _TEXT segment. /NOP Does not pack contiguous logical code segments into a single physical segment. /O:n Controls the interrupt number used by the overlay manager supplied with some Microsoft high level languages. Relevant for real mode programs only. /PAC[:n] Packs contiguous logical code segments into a single physical code segment. N is the maximum size for each packed physical code segment (default = 65,536 bytes). Segments in different groups are not packed. /PADC:n Adds n filler bytes to the end of each code module, so that a larger module can be inserted later with ILINK (incremental Linker). /PADD:n Adds n filler bytes to the end of each data module, so that a larger module can be inserted later with ILINK (incremental Linker). /PAU Pauses during linking, allowing a change of diskettes before the EXE file is written. /SE:n Sets the maximum number of segments in the linked program (default = 128). /ST:n Sets stack size of program in bytes; ignores stack segment size declarations within object modules. /W Displays warning messages for offsets that are relative to a segment base that is not the same as the group base. Relevant to segmented executable files (Windows and OS/2) only. Figure 4-5. Switches accepted by Linker versions 5.01 and later. Using the CREF Utility The CREF cross-reference utility processes a CRF file produced by the assembler, creating an ASCII text file with the default extension REF. The file contains a cross-reference listing of all symbols declared in the program and the line numbers in which they are referenced (see Figure 4-6). The line numbers given in the cross-reference listing correspond to the line numbers that are generated by the assembler in the program listing (LST) filenot to any physical line count in the original source file. Microsoft Cross-Reference Version 5.10 Thu Feb 09 10:04:22 1989 HELLO -- Display Message on stdout Symbol Cross-Reference (# definition, + modification) Cref-1 @CPU . . . . . . . . . . . . . . 1# 3# @VERSION . . . . . . . . . . . . 1# CODE . . . . . . . . . . . . . . 33 DATA . . . . . . . . . . . . . . 23 DGROUP . . . . . . . . . . . . . 20 35 41 44 DOSEXIT. . . . . . . . . . . . . 18# 51 55 DOSWRITE . . . . . . . . . . . . 17# 45 ERROR. . . . . . . . . . . . . . 47 53# MSG. . . . . . . . . . . . . . . 25# 41 MSG_LEN. . . . . . . . . . . . . 26# 42 PRINT. . . . . . . . . . . . . . 37# 57 61 STDERR . . . . . . . . . . . . . 15# STDIN. . . . . . . . . . . . . . 13# STDOUT . . . . . . . . . . . . . 14# 39 WLEN . . . . . . . . . . . . . . 28# 44 _DATA. . . . . . . . . . . . . . 20 23# 30 _TEXT. . . . . . . . . . . . . . 33# 35 59 17 Symbols Figure 4-6. Cross-reference listing HELLO.REF produced by the CREF utility from the file HELLO.CRF, for the HELLO.EXE program example, Figure 3-11 in Chapter 3. The symbols declared in the program are listed on the left in alphabetic order. To the right of each symbol is a list of all of the lines where that symbol is referenced. The number with a # sign after it denotes the line where the symbol is declared. Numbers followed by a + sign indicate that the symbol is modified at the specified line. You can supply parameters for CREF interactively or include them on a single command line. If you enter the name CREF alone, it prompts you for the input and output filenames: [C:\] CREF Microsoft (R) Cross-Reference Utility Version 5.10 Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved. Cross-reference [.CRF]: HELLO Listing [HELLO.REF]: 17 Symbols [C:\] The parameters can also be entered on the command line as follows: CREF crossreffile,[listingfile] For example, the command line equivalent to the interactive session above is the following: [C:\] CREF HELLO; If CREF cannot find the specified CRF file, an error message is displayed. Otherwise, the cross-reference listing is left in the specified file on the disk. To direct CREF to send the cross-reference listing directly to a character device (such as the printer) as it is generated, respond with the name of the device at the Listing prompt. Using the Library Manager Although the object modules that are produced by the assembler or by high level language compilers can be linked directly into executable load modules, they can also be collected into special files called object module libraries. The modules in a library are indexed by name and by the public symbols they contain so that the Linker can extract them to satisfy external references in a program. The Microsoft Library Manager (LIB) creates and maintains object module librariesadding, updating, and deleting object modules as needed. The Librarian is also capable of checking a library file for internal consistency, or of printing a table of its contents. Figure 4-7 shows a portion of such a table for the Microsoft C library SLIBC.LIB. The first part of the listing is an alphabetic list of all public names declared in all of the modules in the library. Each name is associated with the object module to which it belongs. The second part of the listing is an alphabetic list of the module names in the library. Each name is followed by the offset of the object module within the library file and its actual size in bytes. Following the entry for each module is a summary of the public names declared within it. LIB follows the command conventions of most other Microsoft programming tools. You must supply it with the name of a library file to work on, one or more operations, the name of a listing file or device, and (optionally) the name of the output library. If no name is specified for the output library, it is given the same name as the input library, and the extension of the input library is changed to BAK. _abort............abort _abs..............abs _access...........access _asctime..........asctime _atof.............atof _atoi.............atoi _atol.............atol _bdos.............bdos _brk..............brk _brkctl...........brkctl _bsearch..........bsearch _calloc...........calloc _cgets............cgets _chdir............dir _chmod............chmod _chsize...........chsize . . . _exit Offset: 00000010H Code and data size: 44H __exit _filbuf Offset: 00000160H Code and data size: BBH __filbuf _file Offset: 00000300H Code and data size: CAH __iob __iob2 __lastiob . . . Figure 4-7. Extract from the table-of-contents listing produced by the Library Manager for the Microsoft C library SCLIBC.LIB. The LIB operations are simply names of object modules, with a prefix character that specifies the action to be taken: - to delete an object module from the library, * to extract a module and place it in a separate OBJ file, or + to add an object module or the entire contents of another library to the program library. The command prefixes can also be combined: -+ has the effect of replacing a module, while *- has the effect of extracting a module into a new file and then deleting it from the library. When the Librarian is invoked with its name alone, it will request the other information it needs interactively. For example: [C:\] LIB Microsoft (R) Library Manager Version 3.11 Copyright (C) Microsoft Corp 1983-1988. All rights reserved. Library name: SLIBC Operations: +VIDEO List file: SLIBC.LST Output library: SLIBC2 [C:\] In this case, the object module VIDEO.OBJ was added to the library SLIBC.LIB, a library table of contents was written into the file SLIB.LST, and the resulting new library was named SLIBC2.LIB. The Library Manager can also be run with a command line as follows: LIB library [operations],[list],[newlibrary] For example, the following command line is equivalent to the preceding interactive session: [C:\] LIB SLIBC +VIDEO,SLIBC.LST,SLIBC2 As with the other Microsoft utilities, a semicolon at the end of the command line causes the default responses to be used for any parameters that are omitted. Like the Linker, the Librarian is capable of accepting its commands from a response file. The contents of the file are simply lines of text that correspond exactly to the responses you would give LIB interactively. Specify the name of the response file on the command line with a leading @ character: LIB @filename LIB has only three options: /I (/IGNORECASE), /N (/NOIGNORECASE), and /PAGESIZE. Using /NOIGNORECASE causes LIB to regard symbols that differ only in the case of their component letters as distinct. (The default is to ignore case.) The /PAGESIZE switch is used in the following form: /PAGESIZE:number It is placed immediately after the library filename. Specify the library page size in bytes as a power of 2 between 16 and 32,768 (16, 32, 64...); the default is 16 bytes. The page size defines the size of a unit of allocation space for a given library. Because the index to a library is always a fixed number of pages, setting a larger page size allows you to store more object modules in that library; it will, on the other hand, result in more unused space within the file. Using BIND and API.LIB The BIND utility converts a protected mode segmented executable file into a file that can be executed in either protected mode or real mode. BIND accomplishes this somewhat magical feat by appending a real mode loader to the program, along with a real mode routine specific to each API service that it uses. When the file is used in real mode, the API-specific procedures remap the API call parameters into MS-DOS or ROM BIOS function calls. When the file is loaded in protected mode, the real mode loader and other routines appended by BIND are simply ignored. The BIND utility can process any protected mode program. However, for the file to be executed successfully, it must meet the following conditions: The program cannot use any OS/2 kernel services that are not members of the Family API when it is running in real mode. The entire program and the code appended to it by BIND must fit into the available "conventional" memory space (below 640 KB). BIND is command line drivenit does not have an interactive mode, nor can it handle response files. The BIND utility is invoked with an entry of the following form: BIND infile [file.LIB...] [file.OBJ ...] [options] Under normal conditions, the two libraries API.LIB and OS2.LIB must be specified in the command line. Other libraries, particularly dynlink reference libraries, might also be necessary. BIND does not search the directories named in the LIB environment variable, so the locations of libraries must be explicit. The default extension for infile is EXE; the output of BIND will receive the same filename unless otherwise specified with an options switch. The BIND options are listed in Figure 4-8 on the following page. Option Description /o outfile Specifies the name of the output EXE file (default = same name as input EXE file) /n name(s) Specifies one or more "protected mode only" functions to be mapped to the BadDynLink function /n @filename Specifies the name of a file containing one or more "protected mode only" function names to be mapped to the BadDynLink function /m Produces a map file named outfile.BM Figure 4-8. Options for the BIND utility, version 1.1. Using the MAKE Utility The primary function of the MAKE utility is to compare dates of files and to carry out commands based on the result of that comparison. Because of this single, rather basic capability, MAKE can be used to maintain complex programs built from many modules. The dates of source, object, and executable files are simply compared in a logical sequence, and the assembler, compiler, linker, and other programming tools are invoked as appropriate. The MAKE utility processes a plain ASCII text file called, as you might expect, a "make" file. The utility is command line driven and is started with an entry of the following form: MAKE makefile [options] By convention, a make file has the same name as the executable file which is being maintained, but it takes no extension. The available MAKE options are listed in Figure 4-9. Option Description /D Displays last modification date of each file as it is processed /I Ignores exit ("return") codes returned by commands and programs executed as a result of dependency statements /N Displays commands that would be executed as a result of dependency statements but does not execute those commands /S Does not display commands as they are executed /X filename Directs error messages from MAKE, or from any program that MAKE runs, to the specified file; if filename is a hyphen (-), error messages are directed to the standard output Figure 4-9. Options for the MAKE utility, version 4.07. A simple make file contains one or more dependency statements separated by blank lines. Each dependency statement can be followed by a list of OS/2 commands in the following form: targetfile : sourcefile ... command command . . . If the date and time of any sourcefile are later than the targetfile, the accompanying list of commands is carried out. Comment lines, which begin with a # character, can be used freely. MAKE can also process inference rules and macro definitions; for further details on these advanced capabilities, see the Microsoft or IBM documentation. A Complete Example Let's put together everything we've learned about using the OS/2 programming tools up to this point. The general process of creating an application program for OS/2, including use of the Resource Compiler (RC) to build a Presentation Manager application (not within the scope of this book), is illustrated in Figure 4-10 on the following page. Assume that we have the source for the HELLO program discussed in Chapter 3 in the file HELLO.ASM, and assume that the definition file for the same program is named HELLO.DEF. Assume further that both files are located in the current directory. To assemble the source program into the relocatable object module HELLO.OBJ, producing a program listing in the file HELLO.LST and a cross-reference data file HELLO.CRF, we would enter the following command: [C:\] MASM HELLO,,HELLO,HELLO To convert the relocatable object file HELLO.OBJ into the executable file HELLO.EXE, using the module definition file HELLO.DEF and the import library OS2.LIB, and to create a load map in the file HELLO.MAP, we would enter the following: [C:\] LINK HELLO,,HELLO,OS2,HELLO Ŀ Ŀ Ŀ MASM source C or other code file HLL source code file MASM Compiler Ŀ Ŀ Relocatable C or other object module HLL source file (OBJ) code file  LIB RC Ŀ Ŀ Object module Ĵ Resources library file  (LIB) Ŀ Ŀ LINK Protected Code View OS2.LIB  mode application Ŀ Other import Ŀ libraries  Ĵ APLLIB Ŀ Ŀ HLL Family runtime  (dual mode) libraries application Ŀ Module definition  file Figure 4-10. Creation of an OS/2 application program, proceeding from source code to executable file. Because our HELLO program uses only the OS/2 functions DosWrite and DosExit, both of which are members of the Family API, it can be converted to a Family application that runs in either DOS compatibility mode or protected mode. This conversion is performed with the BIND utility and API.LIB by entering the following command: [C:\] BIND HELLO.EXE \LIB\API.LIB \LIB\OS2.LIB OS2.LIB must also be specified because its functions are exported by ordinal numbers rather than by name. Without reference to the ordinals in the OS2.LIB file, BIND can't match the functions imported in the HELLO.EXE header to API.LIB. Note also that BIND.EXE does not use the LIB environment variable, so the locations of API.LIB and OS2.LIB must be explicit (including drive name if necessary). The new HELLO.EXE file produced by BIND can execute in either real mode or protected mode on an 80286 or 80386 machine. To truly generalize this program and obtain a HELLO.EXE file that could run on any 80x86-based machine under MS-DOS or OS/2, we would have to replace all 80286-specific instructions in the source code with equivalent sequences that can run on an 8086/88. For example, we would need to replace the instruction push msg_len with the instructions mov ax,msg_len push ax To locate the 80286-specific instructions in a program, simply remove the .286 directive from the source file and reassemble it; each instruction that does not run on an 8086/88 processor is then flagged as an error. To automate the entire process we have described, we could create a make file named HELLO (with no extension) that contains the following: HELLO.OBJ : HELLO.ASM MASM /T HELLO,,HELLO,HELLO HELLO.EXE : HELLO.OBJ HELLO.DEF LINK HELLO,,HELLO,OS2,HELLO BIND HELLO.EXE \LIB\API.LIB \LIB\OS2.LIB Then, whenever we change either HELLO.ASM or HELLO.DEF and want to rebuild the executable HELLO.EXE file, we need only enter the following: [C:\] MAKE HELLO Note that the /T switch in the MASM command line suppresses all messages unless errors are encountered during assembly. SECTION 2 PROGRAMMING THE USER INTERFACE Chapter 5 Keyboard and Mouse Input The two primary means of user input in OS/2 are the keyboard and mouse. Keyboard support is, of course, always available; the base keyboard driver is always loaded during system initialization. In contrast, loading of the mouse driver is optional; the user must select the appropriate mouse driver by adding a DEVICE directive to the system's CONFIG.SYS file. The OS/2 retail package includes drivers for several brands of mice, including all models of the Microsoft Mouse. Keyboard Methods and Modes A Kernel application can obtain input from the keyboard in three ways: Kbd (keyboard subsystem) API calls DosRead with the predefined handle for the standard input (0) DosRead with a handle obtained by opening logical device CON or KBD$ The Kbd functions provide the most flexibility in keyboard management. Functionally, they are a superset of the ROM BIOS keyboard driver (Int 16H) calls that real mode DOS applications commonly use. Ordinarily, the second method, calling DosRead with the standard input handle, is used only by applicationssuch as filters, compilers, and linkersthat process simple text streams and do not require rapid and flexible interaction with the user. The user can easily redirect the standard input to a file or character device other than the keyboard. The last method, DosRead with a handle obtained by an explicit open of CON or KBD$, is rarely used. It is appropriate for a program that processes simple text streams but must circumvent redirection, or that needs to use DosRead and also perform DosDevIOCtl calls to the keyboard driver. Each screen group has its own default logical keyboard, and the driver maintains a distinct state for each keyboard. (Additional logical keyboards can be created by applications, as will be discussed later in the chapter.) The keyboard state affects the behavior of all the keyboard input functions and includes the status of the shift keys and toggles, the turnaround character (logical end-of-line character, usually the Enter key), an echo flag, and an input mode. The input mode is the element of the keyboard state that is most likely to concern the application programmer. A particular logical keyboard is either in ASCII ("cooked") mode or binary ("raw") mode at any given time. In ASCII mode (the default condition), the key combinations Ctrl-C, Ctrl-Break, Ctrl-S, Ctrl-P, and Ctrl-PrtSc are detected by the operating system and receive special treatment. The echoing of characters to the display and the handling of "extended" keys and the turnaround character depend on the input function being used. In binary mode, characters are never echoed to the display. The turnaround character, extended keys, and special key combinations Ctrl-C, Ctrl-Break, Ctrl-S, Ctrl-P, and Ctrl-PrtSc are passed through to the application unchanged. NOTE: The special characters Ctrl-Esc, Alt-Esc, Ctrl-Alt-Del, Ctrl-Alt-NumLock, and PrtSc are always trapped by the operating system, regardless of the input mode, and an application cannot read or intercept them. Input with Kbd Functions The Kbd functions provide a variety of keyboard input and control services (Figure 5-1). These range in complexity from single character input to installation of custom code pages (tables that map the keyboard to specific character codes). Function Description Keyboard Input KbdCharIn Reads character without echo KbdPeek Returns next character (if any) without removing it from the input buffer KbdStringIn Reads a buffered line with echo and editing capability KbdFlushBuffer Discards all waiting characters KbdXlate Translates scan code and shift states to character Logical Keyboards KbdOpen Creates logical keyboard KbdGetFocus Binds logical keyboard to physical keyboard KbdFreeFocus Releases logical to physical keyboard bonding KbdClose Destroys logical keyboard Keyboard Status KbdGetCp Returns keyboard code page ID KbdGetStatus Returns keyboard state and flags Keyboard Configuration KbdSetCp Selects keyboard code page KbdSetCustXt Installs custom code page KbdSetStatus Configures keyboard state and flags KbdSynch Synchronizes keyboard access Register/Deregister Keyboard Subsystem KbdRegister Registers alternative keyboard subsystem KbdDeRegister Deregisters alternative keyboard subsystem Figure 5-1. The Kbd API at a glance. The prototypal Kbd input operation is KbdCharIn, which returns an ASCII character code, a scan code, a time stamp, and a word of flags for the keyboard shift keys and toggles. The character is not echoed to the display. Extended keys return a 00H or an E0H in the ASCII character field; the application must inspect the scan code to identify the key. KbdCharIn has a wait/no-wait option, which allows the caller to specify whether the function should block until a key is ready or return immediately if no key is available. A related function, KbdPeek, returns the same information as KbdCharIn but does not remove the character from the keyboard input buffer. It allows an application to test keyboard status and to "look ahead" by one character. Avoid using KbdPeek to "poll" the keyboard, however; such polling can severely degrade system performance. KbdStringIn provides buffered line input. It is called with the address of a buffer, a wait/no-wait parameter, and the address of a structure that contains the buffer length and the amount of data already in the buffer which can be used as an editing template (if any). Upon return, the function places the actual number of characters read in the same structure. If KbdStringIn is called and the keyboard is in ASCII mode, the editing keys are active and all other extended keys are ignored and thrown away. Each character is echoed to the display unless the echo flag is off. Tabs are expanded to spaces using 8-column tab stops when they are echoed, although they are left as the code 09H in the buffer. Input is terminated by the Enter key (placed in the buffer as the single character 0DH) or by the buffer's reaching capacity: When one less than the requested length has been read, additional keystrokes are discarded and a warning beep is sounded until the user presses Enter. The no-wait option is not available in ASCII mode. In binary mode, KbdStringIn returns when the buffer is full, keys are not echoed to the display, and the editing keys are not active. Extended keys are placed in the buffer as a 2-byte sequence: a 00H or an E0H byte followed by the scan code. If a program calls KbdStringIn in binary mode with the no-wait option, the function returns immediately with whatever keys are already waiting (up to the maximum requested). Figures 5-2 and 5-3 illustrate the use of the KbdCharIn and KbdStringIn calls. ; this structure receives ; information from KbdCharIn kbdinfo db 0 ; ASCII character code db 0 ; scan code db 0 ; character status db 0 ; NLS shift status dw 0 ; keyboard shift status dd 0 ; time stamp . . . ; read a character push ds ; address of structure push offset DGROUP:kbdinfo push 0 ; 0 = wait for character ; 1 = no wait push 0 ; default keyboard handle call KbdCharIn ; transfer to OS/2 or ax,ax ; did read succeed? jnz error ; jump if function failed . . . Figure 5-2. Reading a character from the keyboard with KbdCharIn. If the no-wait option is used, bit 6 of the character status byte is set if a character was obtained; bit 6 is clear if no character was ready. buffer db 80 dup (0) ; keyboard data goes here buflen dw 80 ; contains length of buffer dw 0 ; contains length of data in ; buffer for editing and ; receives length of data . . . ; indicate nothing is in ; buffer to be edited... mov word ptr buflen+2,0 push ds ; input buffer address push offset DGROUP:buffer push ds ; control structure address push offset DGROUP:buflen push 0 ; 0 = wait for data ; 1 = no wait push 0 ; default keyboard handle call KbdStringIn ; transfer to OS/2 or ax,ax ; did read succeed? jnz error ; jump if function failed . . . Figure 5-3. Reading a buffered line from the keyboard with KbdStringIn. Note that the second word of the buflen structure controls whether the user can edit data already in the buffer (typically from the previous call to KbdStringIn). Input with DosRead The general-purpose function DosRead accepts a handle, a buffer address, a buffer length, and the address of a variable. The handle must be the system's predefined standard input handle, a handle to a file, device, or pipe inherited from the process's parent, or a handle obtained from a successful DosOpen operation. When DosRead returns, the requested data is in the buffer, and the actual number of bytes read is in the specified variable. Figure 5-4 on the following page contains an example of buffered keyboard input using DosRead. When DosRead is called with the standard input handle (and the standard input has not been redirected) or with a handle that is opened to device KBD$, the kernel converts the DosRead into a call to KbdStringIn. However, such a DosRead request behaves somewhat differently from KbdStringIn in two respects. stdin equ 0 ; standard input handle buflen equ 80 ; length of input buffer buffer db buflen dup (0) ; keyboard data goes here rdlen dw 0 ; receives character count . . . push stdin ; standard input handle push ds ; input buffer address push offset DGROUP:buffer push buflen ; maximum characters to read push ds ; receives actual length push offset DGROUP:rdlen call DosRead ; transfer to OS/2 or ax,ax ; did read succeed? jnz error ; jump if function failed . . . Figure 5-4. Reading a buffered line from the keyboard with DosRead. When DosRead returns, rdlen contains the number of characters actually transferred to the buffer. First, in ASCII mode, DosRead translates the Enter key into a 2-character sequence in the buffer: a carriage return (0DH) followed by a linefeed (0AH). In binary mode, DosRead leaves the Enter key in the buffer as a carriage return only. The other difference is more subtle. Regardless of the buffer length specified in the DosRead call, the kernel calls KbdStringIn with a buffer size of 253 characters. This means that the keyboard will not begin to beep and ignore keystrokes until the user has entered 252 keys. However, the number of characters specified in the DosRead call is used as the maximum for the input returned to the application, and any excess characters (possibly including the carriage return and linefeed) are discarded. Although keyboard input with DosRead is appropriate for filterswhich need the capability of redirectable inputthis method is not recommended for interactive applications. The Kbd calls are preferred because they are more efficient, allow closer control of the keyboard, and return more detailed information. If you want to take full advantage of the Kbd interface while allowing input redirection in your application, call DosQHandType with the standard input handle to determine whether the standard input has been redirected. If DosQHandType returns the code for a character device and a device driver attribute word with bit 0 set (1), then the standard input handle has not been redirected, and the application can use the Kbd calls throughout its execution. Otherwise, the application should use DosRead. Keyboard Status and Control Application programs can query or control many aspects of the logical keyboard state, including the code page, the mode (ASCII or binary), the status of shift keys and toggles, the automatic echoing of keys to the display by KbdStringIn, and the turnaround key. Kbd functions are available to inspect or to alter each of these parameters. A similar set of DosDevIOCtl functions is available and can be used by applications that use the DosRead model for keyboard input (Figure 5-5). Function Number Operation 50H Set code page 51H Set input mode 52H Set interim character flags 53H Set shift state 54H Set typematic rate and delay 55H Notify change of foreground session 56H Select Task Manager hot key 57H Bind logical keyboard to physical keyboard 58H Set code page ID 5CH Set national language support and custom code page 5DH Create logical keyboard 5EH Destroy logical keyboard 71H Get input mode 72H Get interim character flags 73H Get shift state 74H Read character 75H Peek character 76H Get Task Manager hot key 77H Get keyboard type 78H Get code page ID 79H Translate scan code to ASCII Figure 5-5. DosDevIOCtl Category 4 functions for keyboard status and control. The most common need for keyboard control in an application is undoubtedly to select ASCII or binary mode. Programs that use the Kbd calls can get or set the mode with KbdGetStatus and KbdSetStatus. These functions use a common data structure that contains much other information, so a program should call KbdGetStatus first to fill the structure with valid data. The ASCII/binary mode bits can then be modified and the structure written back to the system with KbdSetStatus (Figure 5-6). ; keyboard info structure kdbinfo dw 10 ; length of structure dw 0 ; various mode bits dw 0 ; logical end-of-line character dw 0 ; interim character flags dw 0 ; keyboard shift states oldmode dw 0 ; previous keyboard mode . . . ; get keyboard status... push ds ; address of structure push offset DGROUP:kbdinfo push 0 ; default keyboard handle call KbdGetStatus ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed mov ax,kbdinfo+2 ; save current input mode so mov oldmode,ax ; we can restore it later... ; turn off ASCII bit and ; turn on binary mode bit and word ptr kbdinfo+2,0fff7h or word ptr kbdinfo+2,4 ; now set keyboard status... push ds ; address of structure push offset DGROUP:kbdinfo push 0 ; default keyboard handle call KbdSetStatus ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed . . . Figure 5-6. Putting the default logical keyboard into binary mode with KbdGetStatus and KbdSetStatus so that the application can read Ctrl-C and other special characters. The original mode is saved so that it can be restored later. Programs that use DosRead for keyboard input can manipulate the mode with DosDevIOCtl Category 4 Functions 71H (Get Mode) and 51H (Set Mode). These functions can be used only with a handle obtained by opening KBD$ with DosOpen; if you use them with the handle for the standard input or with a handle from a DosOpen of CON, the system returns error code 22 Bad Command (Figure 5-7). kbdname db 'KBD$',0 ; keyboard device name khandle dw 0 ; keyboard handle oldmode db 0 ; previous keyboard mode newmode db 80h ; new keyboard mode ; 0 = ASCII, 80H = binary . . . ; get keyboard handle... push ds ; address of device name push offset DGROUP:kbdname push ds ; receives handle push offset DGROUP:khandle push ds ; receives DosOpen action push offset DGROUP:kaction push 0 ; file size (not applicable) push 0 push 0 ; file attribute (ditto) push 1 ; action = open, no create push 42h ; mode = r/w, deny none push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; didopen succeed? jnz error ; jump if function failed . . . ; get current input mode so ; we can restore it later... push ds ; data packet pointer push offset DGROUP:oldmode push 0 ; parameter packet addr. (none) push 0 push 71h ; function code push 4 ; category code push khandle ; device handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed ; now set binary mode... push 0 ; data packet pointer (none) push 0 push ds ; parameter packet pointer push offset DGROUP:newmode push 51h ; function code push 4 ; category code push khandle ; device handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed . . . Figure 5-7. Putting the keyboard into binary mode with DosDevIOCtl Category 4 Function 51H so that the application can read Ctrl-C and other special characters. The original mode is obtained first with Category 4 Function 71H so that it can be restored later. Note that DosDevIOCtl cannot be used with the standard input handle. Using Logical Keyboards Each screen group has a default logical keyboard that any process can access without further preliminaries using the Kbd calls and keyboard handle 0 or using DosRead with the standard input handle (0). When a user brings a screen group to the foreground, its logical keyboard is "bound" to the physical keyboard, and the processes in that group can read any keys the user presses. When a screen group is in the background, processes in the group that attempt to read from the keyboard are blocked. Processes that use the default logical keyboard are vulnerable to interference by other processes in the same group that also use the default. For example, if a process is interacting with the user and performing key-by-key input, and another process also requests a key, the input received by both programs will be unpredictable. To circumvent this problem, a program can create a new logical keyboard for its screen group with KbdOpen, bind that logical keyboard to the physical keyboard with KbdGetFocus, and then perform its Kbd input operations with the new handle obtained from KbdOpen (Figure 5-8). Keyboard input operations by other processes in the same screen group that use the handle for the default keyboard (0) or any other logical keyboard will be blocked while a KbdGetFocus is in effect. When the process finishes obtaining input, it can use KbdFreeFocus to sever the bond between its logical keyboard and the physical keyboard, thereby allowing other processes to proceed with Kbd operations. The logical keyboard created with KbdOpen can be destroyed with KbdClose; in any event, it is destroyed when the process terminates as part of the kernel's normal cleanup. khandle dw 0 ; receives handle for ; new logical keyboard . . . ; create a logical keyboard push ds ; receives keyboard handle push offset DGROUP:khandle call KbdOpen ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . ; ready for input, get ; keyboard focus... push 0 ; 0 = wait if necessary push khandle ; keyboard handle call KbdGetFocus ; transfer to OS/2 or ax,ax ; did we get focus? jnz error ; jump if function failed . . ; perform input here... . ; done with input, release ; keyboard focus... push khandle ; keyboard handle call KbdFreeFocus ; transfer to OS/2 or ax,ax ; did release succeed? jnz error ; jump if function failed . . . ; logical keyboard no longer ; needed, destroy it... push khandle ; keyboard handle call KbdClose ; transfer to OS/2 . . . Figure 5-8. Creating and using a new logical keyboard to prevent keyboard interference by other processes in the same screen group. Keyboard Subsystems and Drivers The OS/2 substructure for the keyboard support visible to an application is divided among several modules (Figure 5-9). The character device driver KBD$ (loaded from the file KBD01.SYS or KBD02.SYS) services keyboard interrupts, reads keyboard scan codes from the keyboard controller, and interprets those scan codes as characters according to the code page for the current screen group. KBD$ also cooperates with the kernel's monitor dispatcher to create and maintain device monitor chains for each screen group. The keyboard driver runs in kernel mode (ring 0). The dynlink library BKSCALLS.DLL contains the Basic Keyboard Subsystem, which implements the Kbd services. BKSCALLS communicates with the KBD$ driver through DosDevIOCtl calls only; it does not issue DosRead or DosWrite calls. The dynlink library KBDCALLS.DLL is called the "keyboard router." It contains the entry points for the Kbd API and passes the calls onward to the appropriate subsystem (such as BKSCALLS) on a per-screen-group basis. Both BKSCALLS and KBDCALLS run in user mode and thus access the keyboard hardware only indirectly. Although BKSCALLS is the default keyboard subsystem for each screen group, part or all of its services can be replaced on a per-screen-group basis. To activate a new subsystem, call KbdRegister with a dynlink library name, an entry point name, and a set of flags that indicate which Kbd services the module supports; those functions that the module does not support continue to be passed to BKSCALLS. A call to KbdDeRegister deactivates the previously registered subsystem and causes all Kbd processing to revert to BKSCALLS. Ŀ Application Ŀ  Kbd API calls Ŀ KDBCALLS.DLL  DosDevIOCtl Private call interface or Ŀ DosRead BKSCALLS.DLL  DosDevIOCtl Ŀ OS/2 kernel   Request packet interface Ŀ KBD$ device driver  Hardware-dependent I/O operations Ŀ Keyboard controller Figure 5-9. The system modules responsible for keyboard support. The role of device monitors is not shown here. Keyboard Monitors Several OS/2 character device drivers, including the keyboard driver, support a special class of applications called device monitors. A process that registers with the keyboard driver as a monitor can inspect and modify the raw keyboard data stream on a per-screen-group basis. Multiple applications can be registered concurrently as keyboard monitors and can even specify where they want to be located in the chain of all monitors. This facility makes it easy to write "well-behaved" keyboard enhancers, macro processors, and popup utilities. In Chapter 18, keyboard device monitors are discussed in more detail, and a working sample program is presented. For now, simply note the following: A monitor receives a data packet for each keyboard event (press or release of any key); the packet contains the original scan code, the translated ASCII code (if any), a time stamp, the keyboard shift state, and a word of flags. A monitor can consume, modify, or insert keyboard data packets. A monitor is responsible for passing the keyboard data through its monitor buffers promptly to avoid interfering with other processes in the screen group it is monitoring. (A dedicated thread with time-critical priority is usually created for this purpose.) A process can register as a keyboard monitor for a screen group other than the one in which it is running and as a monitor for multiple screen groups. Mouse Input In keeping with its emphasis on graphical user interfaces and pointing devices, OS/2 provides applications with a variety of mouse support functions (Figures 5-10 and 5-11). The full list of Mou API services might appear intimidating, but the average application needs only a few of them. Function Description Enable/Disable Mouse Support MouOpen Obtains mouse handle MouClose Releases mouse handle Mouse Event Messages MouReadEventQue Reads mouse event record (position and button status) MouGetNumQueEl Returns number of waiting mouse event records MouFlushQue Discards any waiting event records Mouse Pointer Control MouGetPtrPos Returns mouse pointer position MouSetPtrPos Sets mouse pointer position MouGetPtrShape Returns mouse pointer shape MouSetPtrShape Defines mouse pointer shape MouDrawPtr Displays mouse pointer MouRemovePtr Defines mouse pointer exclusion area Mouse Driver Status MouGetDevStatus Returns mouse driver status flags MouGetEventMask Returns mouse driver event mask MouGetHotKey Returns mouse button(s) defined as system hot key MouGetNumButtons Returns number of mouse buttons MouGetNumMickeys Returns number of mickeys per centimeter MouGetScaleFact Returns scaling factors for mouse coordinates Mouse Driver Configuration MouInitReal Initializes mouse driver for real mode session MouSetDevStatus Enables or disables pointer drawing and configures position reporting MouSetEventMask Sets mouse driver event mask, controls generation of mouse event queue records MouSetHotKey Defines mouse button(s) as system hot key MouSetScaleFact Sets scaling factors for mouse coordinates MouSynch Synchronizes access to mouse driver Register/Deregister Mouse Subsystem MouRegister Registers alternative mouse subsystem MouDeRegister Deregisters alternative mouse subsystem Figure 5-10. The Mou API at a glance. A process first calls MouOpen to initialize the mouse subsystem for its screen group, if it is not already initialized, and to obtain a logical mouse handle. If the call succeeds, the mouse is "alive" and the application can obtain its state and position; however, the pointer does not appear on the screen until the process calls MouDrawPtr. The process can then use MouReadEventQue to obtain mouse event packets from the mouse driver's FIFO queue. These packets contain flags that indicate the type of event (button press, button release, mouse movement, or a combination), a time stamp, and the mouse screen position. MouReadEventQue has a no-wait option so that the application can avoid blocking if no mouse events are waiting. Unlike other OS/2 kernel functions, the MouReadEventQue wait/no-wait parameter is 1 to wait for an event and 0 to return immediately; MouReadEventQue is also unique in passing the wait/no-wait parameter by reference rather than by value. An application can test the number of waiting events with MouGetNumQueEl. As an alternative to MouReadEventQue, an application can simply poll the mouse position at convenient intervals with MouGetPtrPos. This function, however, does not return the current state of the mouse buttons, and frequent polling can (as with KbdPeek) degrade the performance of the entire system. Regardless of whether it uses MouGetPtrPos or MouReadEventQue, the application should create a separate thread to handle the mouse so that keyboard activity does not affect mouse tracking and vice versa. When the application is finished with the mouse, it can call MouClose to release its mouse handle. Otherwise, the OS/2 kernel closes the mouse handle on behalf of the process as part of the normal cleanup at termination. When the last process using the mouse in the screen group terminates or calls MouClose, the mouse subsystem is disabled for the screen group, and the mouse pointer is removed from the screen. Function Number Operation 50H Enables pointer drawing after session switch 51H Notifies of display mode change 52H Notifies of impending session switch 53H Sets scaling factors 54H Sets event mask 55H Sets system hot key button 56H Sets pointer shape 57H Cancels exclusion area (equivalent to MouDrawPtr) 58H Sets exclusion area (equivalent to MouRemovePtr) 59H Sets pointer position 5AH Sets address of protected mode pointer-draw routine 5BH Sets address of real mode pointer-draw routine 5CH Sets mouse driver status 60H Gets number of buttons 61H Gets number of mickeys per centimeter 62H Gets mouse driver status 63H Reads event queue 64H Gets event queue status 65H Gets event mask 66H Gets scaling factors 67H Gets pointer position 68H Gets pointer shape 69H Gets system hot key button Figure 5-11. DosDevIOCtl Category 7 functions for mouse status and control. The majority of these are present to provide the underpinnings for the Mou calls and can be used by applications, but a few are intended only for use by the Task Manager. The default size of the mouse queue is 10 events. If mouse events occur more rapidly than the application can process them, the oldest events are simply overwritten. This will not usually cause a problem because the application is typically interested in the current position of the mouse, not the history of where it has been! The user can increase the size of the mouse event queue with an optional parameter in the DEVICE= statement (in the CONFIG.SYS file) that loads the mouse driver. All processes in a screen group share the same mouse event queue and can read or change the mouse position, event mask, or pointer shape at any time. The Mou functions provide no analog to the KbdGetFocus and KbdFreeFocus calls that would allow a process to prevent interference by other processes in the same group. Mouse support is restricted for programs not running in the Presentation Manager screen group: The MOUSE$ driver works properly in all "standard" text and graphics modes, but the system's mouse cursor can be used only in text modes. If a nonPresentation Manager application selects a graphics mode and intends to use the mouse, it must disable the system's pointer driver with MouSetDevStatus and provide the mouse cursor itself. A small demonstration program, which monitors and displays the mouse's position and status, appears in Figure 5-12. /* MOUDEMO.C A simple demo of the OS/2 mouse API. Compile with: C> cl moudemo.c Usage is: C> moudemo Copyright (C) 1988 Ray Duncan */ #include #define API unsigned extern far pascal API MouReadEventQue(void far *, unsigned far *, unsigned); API MouOpen(void far *, unsigned far *); API MouClose(unsigned); API MouDrawPtr(unsigned); API VioScrollUp(unsigned, unsigned, unsigned, unsigned, unsigned, char far *, unsigned); API VioWrtCharStr(void far *, unsigned, unsigned, unsigned, unsigned); struct _MouEventInfo { unsigned Flags; unsigned long Timestamp; unsigned Row; unsigned Col; } MouEvent ; main(int argc, char *argv[]) { char OutStr[40]; /* for output formatting */ unsigned Cell = 0x0720; /* ASCII space and normal attribute */ unsigned MouHandle; /* mouse logical handle */ unsigned Status; /* returned from API */ int WaitOption = 1; /* 1 = block for event, 0 = do not block */ /* open mouse device */ Status = MouOpen(0L, &MouHandle); if(Status) /* exit if no mouse */ { printf("\nMouOpen failed.\n"); exit(1); } /* clear the screen */ VioScrollUp(0, 0, -1, -1, -1, &(char)Cell, 0); puts("Press Both Mouse Buttons To Exit"); MouDrawPtr(MouHandle); /* display mouse cursor */ do /* format mouse position */ { sprintf(OutStr, "X=%2d Y=%2d", MouEvent.Col, MouEvent.Row); /* display mouse position */ VioWrtCharStr(OutStr, strlen(OutStr), 0, 0, 0); /* wait for a mouse event */ MouReadEventQue(&MouEvent, &WaitOption, MouHandle); /* exit if both buttons down */ } while((MouEvent.Flags & 0x14) != 0x14) ; MouClose(MouHandle); /* release mouse handle */ puts("Have a Mice Day!"); } Figure 5-12. MOUDEMO.C, a simple demonstration program for the Mou API. This program reads mouse events and displays the mouse position; it terminates when both mouse buttons are pressed simultaneously. Mouse Subsystems and Drivers As with the keyboard, the OS/2 mouse support is divided among several modules (Figure 5-13). The character device driver MOUSE$, loaded from one of the MOUSEXX.SYS files, services mouse interrupts and keeps track of the mouse position and button states. The POINTER$ driver, loaded from POINTDD.SYS, maintains the pointer on the screen. This functional division is not visible at the API level. Ŀ Application Ŀ  Mou API calls Ŀ MOUCALLS.DLL  Private call interface DosDevIOCtl Ŀ BMSCALLS.DLL  DosDevIOCtl Ŀ OS/2 kernel   Request packet interface Ŀ Ŀ Private call Ŀ MOUSE$ device interface POINTER$ device driver  driver  Ŀ Hardware-dependent Ŀ Mouse I/O operations Video hardware display Figure 5-13. Simplified diagram of the relationship between the mouse drivers, dynlink libraries, kernel, and applications. The MOUSE$ driver supports device monitors in much the same manner as the keyboard driver. Each time a mouse "event" occurs, a data packet is passed through the chain of monitor buffers that contains the event type, a time stamp, and the mouse position. A process registered as a mouse monitor can consume, modify, or add data packets to the monitor chain; if it is also registered as a keyboard monitor, it can conceivably translate mouse movements to keystrokes and vice versa! The format of the mouse monitor data packet is shown in Chapter 18. At the dynlink library level, the mouse subsystem is organized much like the keyboard subsystem. The dynlink library BMSCALLS.DLL contains the Basic Mouse Subsystem, which implements the Mou services and communicates with the MOUSE$ driver through DosDevIOCtl calls. MOUCALLS.DLL is the "mouse router"; it contains the entry points for the Mou API functions, and it passes incoming calls to the appropriate subsystem (such as BMSCALLS) on a per-screen-group basis. BMSCALLS and MOUCALLS both run in user mode and thus have no direct access to the hardware. Some or all of the default services provided by BMSCALLS can be replaced using MouRegister and MouDeRegister. A particularly interesting aspect of the mouse subsystem is that the MOUSE$ and POINTER$ drivers communicate directly using a private far call interface. Although this is a wayward arrangement as far as the overall system architecture is concerned, it allows hardware dependence on mouse type and display type to be segregated into two drivers that can be maintained separately while avoiding excessive overhead for pointer tracking. MOUCALLS.DLL establishes the linkage between the mouse and pointer drivers at the time of a MouOpen request. MOUCALLS first invokes DosOpen for POINTER$, and then it calls DosDevIOCtl Category 3 Function 72H to get the entry point. MOUCALLS then performs a DosOpen for MOUSE$, followed by a call to DosDevIOCtl Category 7 Function 5AH to pass it the POINTER$ entry address. Chapter 6 The Video Display OS/2 provides application programs with a rich selection of function calls to control the video display. These functions span the complete spectrum of hardware independence: Presentation Manager graphical services DosWrite with the predefined handle for standard output (1) or standard error (2) DosWrite with a handle obtained by opening the logical device CON or SCREEN$ The Vio (video subsystem) functions Direct access to the screen group's logical video buffer Direct access to the physical video buffer The Presentation Manager provides applications with a uniform graphical user interface. It supports text fonts, windowing and clipping, pull-down menus, popup dialog boxes, pointing devices, and a broad range of high performance drawing and painting operations. To take full advantage of these capabilities, applications must have a special structure and must abide by an intricate set of conventions. The payoff is complete portability between machines and output devices that support the Presentation Manager, invisible adjustment of the program's output to compensate for the display's resolution and aspect ratio and to exploit available fonts, and a shortened learning curve for the user. When writing character-oriented applications, you can use the general purpose function DosWrite to display text. You can control the video mode, foreground and background colors, and cursor position with ANSI escape sequences embedded in the output. DosWrite is analogous to Int 21H Function 40H (Write to File or Device) under MS-DOS and is most appropriate for filters and other utility programs in which the ability to redirect output is important and interactive performance is not an issue. Programs that use this function avoid the complexity of the Presentation Manager graphical interface but retain device independence and the ability to run in a PM window. You can use the Vio functions, which offer more flexibility and speed than DosWrite, for interactive character-oriented applications that do not require redirectable output. The Vio functions provide such capabilities as scrolling the screen in all four directions, controlling cursor shape, assigning character attributes and colors, and reading strings back from the screen buffer. The Vio calls are a functional superset of the ROM BIOS video driver (Int 10H) calls available under MS-DOS; they are immune to redirection of the standard output and (with the exception of one function) ignore control codes and escape sequences. Applications that use a defined subset of the Vio calls and avoid other hardware dependence will run in a PM window. Last, we come to the two hardware-dependent display methods. You might use them in applications that have special requirements, such as a need to present a graphics display without the aid of the Presentation Manager, or to drive the controller in a mode or resolution not supported by OS/2's built-in screen driver. The first of these techniques is to obtain a selector from OS/2 that gives the application direct access to the screen group's logical video buffer (LVB). The LVB is a "shadow" of the screen group's display; when it is not in the foreground, it receives output from programs in that group. After modifying the contents of the LVB, the program issues an additional command to refresh the physical display bufferwith no effect if the application's screen group is in the background. Use of the LVB allows very high display throughput and does not interfere with processes in other screen groups. The second, and potentially more destructive, hardware-dependent technique is to obtain a selector for the physical video buffer from OS/2. After "locking" the screen with a function call, the application can write directly to the buffer, "unlocking" the screen with another function call when it is finished. If the application contains a segment with IOPL (I/O privilege), it can also issue commands directly to the video adapter's I/O ports. While the screen lock is in effect, the user cannot switch to another screen group, and background tasks cannot "pop up" to interact with the user. This chapter will focus on the DosWrite and Vio calls used by Kernel applications to display text and control the screen. The programming of true Presentation Manager applications is beyond the scope of this book. At the other extreme, the hardware-dependent methods tend to be incompatible with the Presentation Manager and should therefore be avoided. Video Display Modes With one exceptionthe original Monochrome Display Adapter (MDA) the video adapters used in OS/2-compatible personal computers support two or more display modes (Figure 6-1). The modes fall into two distinct classes: alphanumeric, or text, modes and all-points-addressable (APA), or graphics, modes. The video adapters have a hybrid interface to the CPU and are controlled by a mixture of memory-mapped and port-addressed I/O. Resolution Colors Text/ MDA MDA = Monochrome Display Adapter CGA CGA = Color/Graphics Adapter EGA EGA = Enhanced Graphics Adapter VGA VGA = Video Graphics Array Graphics 40-by-25 16 Text    80-by-25 2 Monochrome monitor only Text    80-by-25 16 Text    80-by-43 16 Text   80-by-50 16 Text  320-by-200 4 Graphics    320-by-200 16 Graphics   320-by-200 256 Graphics  640-by-200 2 Graphics    640-by-200 16 Graphics   640-by-350 2 Monochrome monitor only Graphics   640-by-350 4 Graphics  EGA with 64 KB of RAM 640-by-350 16 Graphics  EGA with 128 KB or more of RAM  640-by-480 2 Graphics  640-by-480 16 Graphics  Figure 6-1. The video modes available on various video adapters used in OS/2-compatible computers. In text modes, each character on the screen is represented by two adjacent bytes in a buffer, called the refresh (or "regen") buffer, that is dedicated to use by the video controller. The first byte of such a pair contains the character code, and the second byte contains the character "attribute." The attribute controls special display characteristics such as the foreground and background colors, blink, and underline. A hardware character generator translates the ASCII code into a pattern of pixels on the screen at a column and row corresponding to the character's position in the refresh buffer. In graphics modes, the bits in the refresh buffer are mapped directly to the pixels on the screen. This bit mapping might differ drastically between graphics modes with different resolutions. No hardware character generator is available to translate ASCII codes to an array of pixels; instead, the software itself must turn the bits in the buffer on or off to form a character or figure on the display. OS/2's support for graphics modes is, for the most part, exploitable only within a Presentation Manager application. Kernel or Family applications that select a graphics mode cannot run in a PM window and must contain their own software character generators and graphics drawing primitives. Using DosWrite for Output The general purpose function DosWrite accepts a handle for a file or device, the address of the data to be written, the length of the data (up to 65,535 bytes), and the address of a variable. DosWrite returns the actual number of bytes written in the specified variable (Figure 6-2). stdout equ 1 ; standard output handle msg db 'HELLO' ; message to be displayed msg_len equ $-msg ; length of message wlen dw ? ; receives actual number ; of bytes written . . . push stdout ; standard output handle push ds ; address of text push offset DGROUP:msg push msg_len ; length of text push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; was write successful? jnz error ; jump if function failed . . . Figure 6-2. Writing text to the display using DosWrite and the standard output handle. When you use DosWrite to display text on the screen, the handle must be either one of the system's predefined handles for standard output (1) or standard error (2), or a handle obtained from a successful DosOpen of the logical device CON or SCREEN$. The DosOpen method is typically used by processes that need to circumvent any redirection of the standard devices that might be in effect (Figure 6-3). sname db 'CON',0 ; device name shandle dw ? ; receives device handle action dw ? ; receives DosOpen action wlen dw ? ; receives bytes written msg db 'HELLO' ; message to be displayed msg_len equ $-msg ; length of message . . . push ds ; address of device name push offset DGROUP:sname push ds ; variable to receive handle push offset DGROUP:shandle push ds ; variable to receive action push offset DGROUP:action push 0 ; filesize (not applicable) push 0 push 0 ; attribute (not applicable) push 1h ; action = open, no create push 42h ; mode = rd/wt, deny-none push 0 ; reserved (0) push 0 call DosOpen ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . push shandle ; handle for CON push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push ds ; address of variable push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 6-3. Bypassing output redirection by opening the screen driver as if it were a file and using the new handle obtained for subsequent text output. DosWrite treats the display as a "glass teletype." It provides line wrapping and screen scrolling; and carriage returns, linefeeds, tabs, bell codes, and backspaces embedded in the output have their expected effects. In general, it always writes the exact number of characters specified. The output terminates prematurely (reflected in the value returned in the variable) only if it is redirected to a disk file and the disk is full, or if the data contains a Ctrl-Z character (1AH). Although DosWrite is appropriate for filters and other programs that process streams of simple text, you should avoid using it in interactive applications. The kernel translates any call to DosWrite that uses a handle for the display to VioWrtTTY anyway, so calling DosWrite is intrinsically less efficient than calling Vio functions directly. Controlling Character Attributes with DosWrite If your program uses DosWrite for display, you can use a subset of the ANSI 3.641979 standard escape sequences to select character attributes and colors, clear the screen, and position the cursor. Escape sequences are so called because they always start with the ASCII ESC character code (1BH). You can embed these sequences within other text strings or send them to the screen driver separately with DosWrite. Before you use escape sequences, verify that ANSI support is enabled with the functions VioGetAnsi and VioSetAnsi. Character attributes, such as reverse, high intensity, or underline on monochrome adapters or foreground and background colors on color adapters, are controlled with the escape sequences of the following form: [nm where n has the values shown in the following table. Value of n Attribute 0 No special attributes 1 High intensity 2 Low intensity 3 Italic 4 Underline 5 Blink 7 Reverse video 8 Concealed text (no display) For color control, you can also replace n with the following values: Foreground Background Color 30 40 Black 31 41 Red 32 42 Green 33 43 Yellow 34 44 Blue 35 45 Magenta 36 46 Cyan 37 47 White To specify multiple attributes in the same escape sequence, separate them with semicolons. After you select an attribute, it is applied to all subsequent text written to the screen until another escape sequence alters it. For example, the code illustrated in Figure 6-4 causes all subsequent text to be displayed in reverse video. stdout equ 1 ; standard output handle revstr db 01bh,'[7m' ; escape sequence to ; select reverse video rev_len equ $-revstr ; length of escape sequence wlen dw ? ; receives actual number ; of bytes written . . . push stdout ; standard output handle push ds ; address of escape sequence push offset DGROUP:revstr push rev_len ; length of escape sequence push ds ; address of variable push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 6-4. Using an escape sequence to select the reverse video attribute for all text that is subsequently displayed. Clearing the Screen and Scrolling with DosWrite Two escape sequences are available to clear all or part of the screen. The first such string clears the screen and positions the cursor in the upper left corner: [2J The other string clears from the cursor to the end of the current line and leaves the cursor position unchanged: [K You can scroll the screen upward with DosWrite by positioning the cursor on the last line of the screen and then sending a linefeed (0AH) character. The entire new line receives the attribute of the last character on the preceding line. Scrolling in the other three directions is not supported for output with DosWrite. Cursor Control with DosWrite The cursor can be positioned with the sequence [row;colH where row is the y coordinate and col is the x coordinate. The row and col values are one-based, decimal numbers expressed as ASCII strings and are not binary values. Position (x,y) = (1,1) is the upper left corner of the screen; the maximum values for the x and y coordinates depend on the current display mode. While the ANSI screen driver uses (1,1) as the home position, nearly all other OS/2 services use (0,0) as the home coordinate. Throughout the remainder of the book, assume that screen addressing is zero-based unless explicitly noted otherwise. Escape sequences ending with f have the same effect as those ending with H. An example of cursor positioning is provided in Figure 6-5. stdout equ 1 ; standard output handle movstr db 01bh,'[25;1H' ; escape sequence to move ; cursor to (x,y) = (1,25) mov_len equ $-movstr ; length of escape sequence wlen dw ? ; receives actual number ; of bytes written . . . push stdout ; standard output handle push ds ; address of escape sequence push offset DGROUP:movstr push mov_len ; length of escape sequence push ds ; address of variable push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 6-5. Using an ANSI escape sequence to position the cursor at screen coordinates (0,24); in other words, the first column of the last line on the screen. Note that coordinates in escape sequences are one-basedexpressed relative to a home position of (1,1). Escape sequences also exist to move the cursor relative to its current position; these sequences do not scroll the screen if the cursor reaches the edge: [nAMove cursor up n rows [nBMove cursor down n rows [nCMove cursor right n columns [nDMove cursor left n columns To determine the current cursor position, the program can send the following sequence: [6n The program can then use DosRead to obtain the cursor position from the standard input in the format [row;colR where row and col are one or more ASCII digits. For example, if the cursor is positioned at the one-based coordinates (x,y) = (25,7), the position is reported with the character sequence [25;07R Decoding such position reports is inconvenient at best. In most cases, the need for such decoding can be avoided with the two escape sequences [s [u Note that the driver can "remember" only one position at a time. Mode Control with DosWrite You can use the escape sequence [=nh to select the display mode for the active video adapter, where the value of n has the following meanings: Value of n Video Display Mode 0 40-by-25 16-color text (color burst off) 1 40-by-25 16-color text 2 80-by-25 16-color text (color burst off) 3 80-by-25 16-color text 4 320-by-200 4-color graphics 5 320-by-200 4-color graphics (color burst off) 6 640-by-200 2-color graphics The values of n are the same as the video display mode numbers used in the old IBM ROM BIOS. You cannot use an escape sequence to select the higher resolution graphics modes available on an EGA or VGA adapter, to obtain the current display mode, or to activate another adapter if the system contains more than one. Using Vio Functions for Output The Vio subsystem is a high performance, character-oriented display interface for Kernel and Family applications. The Vio functions are summarized by category in Figure 6-6. You can use several different Vio functions to display text. The simplest and most general is VioWrtTTY, which is called with the address and length of a string and a Vio handle. The function returns a status code of zero if the write succeeded or an error code if it failed. Figure 6-7 on page 105 demonstrates a call to the function. Function Description Display VioWrtTTY Not supported in graphics mode in nonPresentation Manager screen groups. Writes string in "teletype" mode VioWrtCellStr Not supported in graphics mode in nonPresentation Manager screen groups. Writes string of alternating characters and attribute bytes VioWrtCharStr Not supported in graphics mode in nonPresentation Manager screen groups. Writes string without attribute control VioWrtCharStrAtt Not supported in graphics mode in nonPresentation Manager screen groups. Writes string applying the same attribute to each character VioReadCellStr Not supported in graphics mode in nonPresentation Manager screen groups. Reads characters and attributes from display VioReadCharStr Not supported in graphics mode in nonPresentation Manager screen groups. Reads characters from display Replication VioWrtNAttr Not supported in graphics mode in nonPresentation Manager screen groups. Replicates attribute byte VioWrtNCell Not supported in graphics mode in nonPresentation Manager screen groups. Replicates character and attribute byte VioWrtNChar Not supported in graphics mode in nonPresentation Manager screen groups. Replicates character Cursor Size and Position VioGetCurPos Not supported in graphics mode in nonPresentation Manager screen groups. Gets cursor position VioSetCurPos Not supported in graphics mode in nonPresentation Manager screen groups. Sets cursor position VioGetCurType Not supported in graphics mode in nonPresentation Manager screen groups. Gets cursor shape and size VioSetCurType Not supported in graphics mode in nonPresentation Manager screen groups. Sets cursor shape and size Scroll or Clear Screen VioScrollDn Not supported in graphics mode in nonPresentation Manager screen groups. Scrolls display down VioScrollLf Not supported in graphics mode in nonPresentation Manager screen groups. Scrolls display left VioScrollRt Not supported in graphics mode in nonPresentation Manager screen groups. Scrolls display right VioScrollUp Not supported in graphics mode in nonPresentation Manager screen groups. Scrolls display up Display Mode Control VioSetAnsi Not supported in graphics mode in nonPresentation Manager screen groups. Enables or disables ANSI support VioSetCp Not supported in graphics mode in nonPresentation Manager screen groups. Selects code page VioSetFont Not supported in graphics mode in nonPresentation Manager screen groups. Restricted or not allowed in Presentation Manager screen groups. Downloads display font VioSetMode Restricted or not allowed in Presentation Manager screen groups. Selects display mode VioSetState Restricted or not allowed in Presentation Manager screen groups. Sets palette, border color, or blink/intensity toggle Mode Information VioGetAnsi Not supported in graphics mode in nonPresentation Manager screen groups. Gets ANSI support state VioGetBuf Not supported in graphics mode in nonPresentation Manager screen groups. Gets selector for logical video buffer VioGetConfig Gets video adapter information VioGetCp Not supported in graphics mode in nonPresentation Manager screen groups. Gets current code page VioGetFont Restricted or not allowed in Presentation Manager screen groups. Gets character dimensions and font VioGetMode Gets current display mode VioGetPhysBuf Restricted or not allowed in Presentation Manager screen groups. Gets selector for physical video buffer VioGetState Restricted or not allowed in Presentation Manager screen groups. Gets palette, border color, and blink/intensity toggle Miscellaneous Functions VioPrtSc Not supported in graphics mode in nonPresentation Manager screen groups. Prints screen VioPrtScToggle Not supported in graphics mode in nonPresentation Manager screen groups. Turns print echo on or off VioScrLock Not supported in graphics mode in nonPresentation Manager screen groups. Disables screen switching VioScrUnLock Restricted or not allowed in Presentation Manager screen groups. Enables screen switching VioShowBuf Not supported in graphics mode in nonPresentation Manager screen groups. Updates physical display buffer from logical buffer Pop-Up Support VioPopUp Allocates full-screen text-mode popup display VioEndPopUp Deallocates popup display Support for Non-PM Graphics Applications VioSavRedrawWait Restricted or not allowed in Presentation Manager screen groups. Returns when screen should be saved or redrawn VioSavRedrawUndo Restricted or not allowed in Presentation Manager screen groups. Cancels VioSavRedrawWait call by another thread VioModeWait Restricted or not allowed in Presentation Manager screen groups. Returns when display mode should be restored VioModeUndo Restricted or not allowed in Presentation Manager screen groups. Cancels VioModeWait call by another thread Advanced Vio Functions VioAssociate Associates Vio presentation space with device context VioCreateLogFont Creates logical font for use with Vio presentation space VioCreatePS Allocates Vio presentation space VioDeleteSetId Releases logical font identifier VioDestroyPS Destroys Vio presentation space VioGetDeviceCellSize Returns character cell height and width VioGetOrg Returns screen coordinates for origin of Vio presentation space VioQueryFonts Returns list of available fonts for specified typeface VioQuerySetIds Returns list of loaded fonts VioSetDeviceCellSize Sets character cell height and width VioSetOrg Sets screen coordinates of origin for Vio presentation space VioShowPS Updates display from Vio presentation space Vio Function Replacement VioRegister Restricted or not allowed in Presentation Manager screen groups. Replaces default Vio functions with new routines VioDeRegister Restricted or not allowed in Presentation Manager screen groups. Deregisters alternative Vio routines Figure 6-6. Vio functions at a glance. The advanced Vio functions are present to support the OS/2 Presentation Manager and are not available in OS/2 version 1.0. msg db 'Hello World' ; message to display msg_len equ $-msg ; length of message . . . push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push 0 ; Vio handle call VioWrtTTY ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 6-7. Using VioWrtTTY to write the string Hello World to the screen at the current cursor position. The cursor position is updated appropriately after the write. VioWrtTTY handles the control characters linefeed, carriage return, backspace, and bell properly. Line wrapping and scrolling are provided (unless they are intentionally disabled), the cursor position is updated, and ANSI escape sequences described for DosWrite are interpreted unless ANSI support is turned off. The attribute for all characters is the one most recently selected with the appropriate ANSI escape sequence; when the screen is scrolled, the entire new line is given the attribute of the last character written to the previous line. Ordinarily, the only error code that VioWrtTTY returns is 436 (Invalid Vio Handle). Providing the function with an invalid string address or length does not usually result in an error code but may well cause a GP fault that terminates the process. Three other Vio calls, which are slightly trickier to use, are also available for displaying text: VioWrtCharStr, VioWrtCharStrAtt, and VioWrtCellStr. These functions are faster than VioWrtTTY and provide direct control over screen placement, but they do not update the cursor position. Their support for line wrapping is limited: If the text is too long for the current line, it wraps onto the next line; however, if the end of the screen is reached, any remaining characters are discarded, the screen is not scrolled, and no error is returned. All three functions ignore ANSI escape sequences or control characters; any nonalphanumeric characters embedded in the string are displayed as their graphics character equivalents. VioWrtCharStr is the simplest of the three functions listed above. It accepts the address and length of a string, the screen position at which to begin writing the string, and a Vio handle. The new text assumes the attributes of the characters previously displayed at the same screen positions. The VioWrtCharStrAtt call is similar to the VioWrtCharStr function; it has, however, one additional parameter: the address of an attribute byte which is applied to every character in the string (as demonstrated in Figure 6-8). On adapters with monochrome monitors, the attribute byte specifies normal or reverse video, intensity (highlight), blinking, and underline (Figure 6-9). On adapters with color monitors in text modes, the attribute byte contains the background color in the upper four bits and the foreground color in the lower four bits (Figure 6-10). attr db 70h ; reverse video attribute msg db 'Hello World' ; message to display msg_len equ $-msg ; length of message . . . push ds ; address of string push offset DGROUP:msg push msg_len ; length of string push 5 ; y position push 10 ; x position push ds ; video attribute address push offset DGROUP:attr push 0 ; Vio handle call VioWrtCharStrAtt; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 6-8. Using the VioWrtCharStrAtt function to write to the screen. This code displays the string Hello World in reverse video at cursor location (10,5) (column 10, row 5). VioWrtCellStr displays a string that consists of alternating character and attribute bytes. VioWrtCellStr is designed to be used in combination with VioReadCellStr to restore an area of the display that was previously saved into a buffer. This function is not appropriate for routine text output for a number of reasons. Generating initialized strings with embedded attribute bytes is awkward in most languages, and such strings are bulky. Furthermore, applications rarely need to display a string where each successive character has a different attribute. 7 6 5 4 3 2 1 0 Ŀ B Background I Foreground Blink (default) or Foreground intensity (default) background intensity or character select Display Background Foreground No display (black) 000 000 No display (white) VGA only 111 111 Underline 000 001 Normal video 000 111 Reverse video 111 000 Figure 6-9. The mapping of attribute bytes for the monochrome display adapter (MDA) or for the EGA or VGA in monochrome text mode. 7 6 5 4 3 2 1 0 Ŀ B Background I Foreground Blink Intensity Value Color Value Color 0 Black 8 Gray 1 Blue 9 Light blue 2 Green 10 Light green 3 Cyan 11 Light cyan 4 Red 12 Light red 5 Magenta 13 Light magenta 6 Brown 14 Yellow 7 White 15 Intense white Figure 6-10. The attribute byte for CGA, EGA, or VGA graphics adapters in color text modes is divided into two 4-bit fields that control the foreground and background colors. The color assignments shown are fixed for the CGA and are the defaults for the EGA or VGA. (Other color assignments can be obtained by programming the palette.) These assignments also assume that the B or I bit controls intensity. The Vio functions VioWrtNChar, VioWrtNAttr, and VioWrtNCell offer some special capabilities that supplement the other text display functions, and they require similar parameters. You can use VioWrtNAttr to modify the attribute bytes of a selected area of the screen without disturbing the text displayed at that position; VioWrtNChar and VioWrtNCell let you draw borders and highlighted areas efficiently in text mode. The Vio functions are not affected by command line redirection parameters. An OS/2 application that wants to take full advantage of the Vio interface while allowing redirection can call DosQHandType with the standard output handle during its initialization to determine whether standard output has been redirected. If DosQHandType indicates that standard output is associated with a file or a pipe, the application should use DosWrite throughout its execution. If the standard output is a character device, and the device attribute word returned by DosQHandType has bit 1 set, the application can proceed to use the Vio calls. Scrolling and Clearing the Screen with Vio The Vio calls allow great flexibility in scrolling or clearing selected areas of the screen. You can use the scroll functions VioScrollUp, VioScrollDn, VioScrollLf, and VioScrollRt to clear a window of arbitrary size and position or to scroll it up, down, left, or right any number of columns or rows. You can specify any character-attribute pair to fill the newly blanked or scrolled lines. All four functions have the following parameters: the screen coordinates of the upper left corner and lower right corner of a window, the number of lines to be scrolled or blanked, the address of a character and attribute pair to fill the blanked lines, and a Vio handle. You can clear the entire display in any mode without first determining the screen dimensions with a special-case call to any of the four scroll functions: Use an upper left coordinate of (0,0), a lower right coordinate of (-1,-1), and the value -1 as the number of lines to scroll. Figure 6-11 demonstrates clearing the screen to ASCII blanks with a normal video attribute. Scrolling a selected portion of the screen is also easily done, as demonstrated in Figure 6-12. cell db 20h,07h ; ASCII blank, normal video . . . push 0 ; y upper left push 0 ; x upper left push -1 ; y lower right push -1 ; x lower right push -1 ; number of blank lines push ds ; char/attrib address push offset DGROUP:cell push 0 ; Vio handle call VioScrollUp ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed . . . Figure 6-11. Clearing the screen to ASCII blanks with a normal video attribute, using one of the four Vio scroll functions. This special-case call with an upper left window coordinate of (0,0), lower right coordinate of (-1,-1), and -1 as the number of lines to scroll does not require you to first determine the dimensions of the active display. cell db 20h,07h ; ASCII blank, normal video . . . push 12 ; y upper left push 0 ; x upper left push 24 ; y lower right push 79 ; x lower right push 1 ; number of lines to scroll push ds ; address of char & attrib push offset DGROUP:cell push 0 ; Vio handle call VioScrollUp ; transfer to OS/2 or ax,ax ; did scroll succeed? jnz error ; jump if function failed . . . Figure 6-12. Scrolling selected portions of the screen in any direction can be accomplished very easily with the VioScrollxx functions. For example, this code scrolls the bottom half of the screen up one line, filling the newly blanked line with ASCII blanks with a normal video attribute, leaving the top half of the screen untouched. Cursor Control with Vio The Vio subsystem supports a comprehensive set of functions to control cursor shape and position. The efficiency of these functions relieves you of the need to access the video hardware directly. Use the function VioSetCurPos to position the cursor. In addition to being faster than the ANSI driver method, VioSetCurPos is also much simpler to use. The zero-based binary screen coordinates are simply pushed onto the stack, instead of being converted to one-based ASCII strings and embedded in an ANSI escape sequence. For example, the code in Figure 6-13 positions the cursor on the first column of the last line of the screen. Use the parallel function VioGetCurPos to obtain the current cursor location (Figure 6-14). Both VioSetCurPos and VioGetCurPos use zero-based text coordinates and assume that the coordinates of the home positionthe upper left corner of the screenare (0,0). The functions VioGetCurType and VioSetCurType obtain or change the cursor height, width, and hidden/visible attribute. Both use a 4-word data structure with the same formatquite convenient when the cursor must be altered or hidden temporarily (Figure 6-15). You can use VioGetMode to determine the vertical resolution and number of text lines in the current screen mode, from which you can calculate the size of the character cell and the valid starting and ending scan lines for the cursor. . . . push 24 ; y coordinate push 0 ; x coordinate push 0 ; Vio handle call VioSetCurPos ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed . . . Figure 6-13. Cursor positioning with the Vio calls. This code places the cursor at position (x,y) = (0,24), that is, on the first column of the twenty-fifth row of the screen. Compare this fragment with Figure 6-5. CurY dw ? ; cursor y position CurX dw ? ; cursor x position . . . push ds ; receives y coord push offset DGROUP:CurY push ds ; receives x coord push offset DGROUP:CurX push 0 ; Vio handle call VioGetCurPos ; transfer to OS/2 or ax,ax ; cursor position obtained? jnz error ; jump if function failed . . . Figure 6-14. Reading the cursor position with VioGetCurPos. The cursor location is returned in text coordinates that assume a home position of (0,0). CurData label word ; cursor data structure CurBeg dw ? ; starting scan line CurEnd dw ? ; ending scan line CurWid dw ? ; width (0 = default) CurAttr dw ? ; cursor attribute ; (0 = visible, -1 = hidden) CurPrev dw ? ; previous cursor start line . . . push ds ; receives cursor info push offset DGROUP:CurData push 0 ; Vio handle call VioGetCurType ; transfer to OS/2 or ax,ax ; cursor info obtained? jnz error ; jump if function failed mov ax,CurBeg ; save cursor starting line mov CurPrev,ax mov CurBeg,0 ; force starting line to ; zero to get block cursor push ds ; contains cursor info push offset DGROUP:CurData push 0 ; Vio handle call VioSetCurType ; transfer to OS/2 or ax,ax ; did we change cursor? jnz error ; jump if function failed . . ; other code goes here... . mov ax,CurPrev ; restore cursor shape mov CurBeg,ax push ds ; contains cursor info push offset DGROUP:CurData push 0 ; Vio handle call VioSetCurType ; transfer to OS/2 or ax,ax ; did we change cursor? jnz error ; jump if function failed . . . Figure 6-15. Example of altering the cursor to a block and later restoring it to its original shape. Mode Control with Vio The functions VioGetMode and VioSetMode query or select the video mode without resorting to the "magic numbers" inherited from the original IBM PC ROM BIOS. Both functions use a data structure that specifies the adapter type, text or graphics mode, color burst enable, and the number of displayable colors, text columns and rows, and pixel columns and rows. This approach is open-ended. It allows the application to deal with the adapter on the basis of its capabilities and doesn't require the programmer to remember an ever-expanding list of "mode numbers" that have less and less relationship to the display modes they represent. For example, the code in Figure 6-16 selects 80-by-25 16-color text mode with color burst enabled and is exactly equivalent to the escape sequence [3h used with DosWrite or VioWrtTTY. ; video mode data structure ModeInf dw 8 ; length of structure db 1 ; non-MDA, text mode, color db 4 ; 16 colors dw 80 ; 80 text columns dw 25 ; 25 text rows . . . push ds ; contains mode info push offset DGROUP:ModeInf push 0 ; Vio handle call VioSetMode ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed . . . Figure 6-16. The functions VioGetMode and VioSetMode interrogate or select the display mode based on the controller's capabilities rather than magic numbers. This code selects 80-by-25 16-color text mode with color burst enabled. Pop-Up Support The functions VioPopUp and VioEndPopUp let a background process temporarily assume control of the screen and interact with the user. Background processes, which are launched with the DETACH command, cannot ordinarily perform Kbd or Mou input calls, and their output to the screen is discarded. A process can determine whether it was started with DETACH by issuing a VioGetAnsi function callthis call returns an error code if output calls will also fail. To gain access to the screen, the background process first calls VioPopUp. This function provides two options: wait or no-wait, and transparent or nontransparent. If wait is specified, the calling process is suspended until the screen is available; if no-wait is selected, the function returns immediately with an error code if the screen cannot be preempted (for example, if another background process has called VioPopUp). If the VioPopUp call is successful, the transparent/nontransparent option comes into play. First, the current contents of the screen (belonging to the foreground process) are saved by the system. If the nontransparent option was selected, the screen is blanked and placed into an 80-by-25 text mode, and the cursor is placed in the home position (x,y = 0,0). Otherwise, no mode change occurs and the screen contents are left intact. In either case, all subsequent Vio calls by the background process are directed to the active display. At this point, the background process can interact freely with the user. Other processes continue to run normally, their output going to the usual logical video buffer until they require input or call VioPopUp, at which point they are blocked. When the background process is finished with its pop-up, it must call VioEndPopUp to release control of the screen. The state of the display at the time of the pop-up is then restored, regardless of whether the transparent or nontransparent option was used. Figure 6-17 contains the skeleton code for a screen pop-up. msg db 'Surprise!' ; message for popup screen msg_len equ $-msg ; length of message option dw 1 ; Option flags for VioPopUp ; bit 0: 1 = wait, 0 = no-wait ; bit 1: 1 = transparent mode ; 0 = nontransparent mode kbddata db 10 dup (0) ; structure for KbdCharIn . . . ; put up popup window... push ds ; address of option flags push offset DGROUP:option push 0 ; Vio handle call VioPopUp ; transfer to OS/2 or ax,ax ; did we capture display? jnz error ; jump if function failed ; display popup message... push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push 12 ; y push (80-msg_len)/2 ; x (center it) push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 ; wait for any key... push ds ; receives keyboard data push offset DGROUP:kbddata push 0 ; 0 = wait for a character push 0 ; default keyboard handle call KbdCharIn ; transfer to OS/2 ; take down popup window... push 0 ; Vio handle call VioEndPopUp ; transfer to OS/2 . ; (should never fail) . . Figure 6-17. A background process using VioPopUp and VioEndPopUp to interact with the user. Note that after a successful call to VioPopUp, the program must be careful not to take any branch away from the main line of execution toward VioEndPopUp. A background process that uses VioPopUp may have difficulty getting access to the screen if the current foreground process is computation-intensive rather than I/O-intensive. This is because the threads of a foreground process get an artificial boost to their priority to keep the user happy, and OS/2's scheduler always chooses the thread with the highest priority that is ready to run. The background process can circumvent this problem by promoting the VioPopUp thread to a time-critical priority then ensuring that this thread is blocking at all other times. Use of VioPopUp should be kept to a minimum and reserved for true background processes. This is because screen group switching is disabled during a VioPopUp...VioEndPopUp sequence, and the abrupt transition from a normal display to the largely blank display of a nontransparent pop-up (with a possible concomitant change in display mode) can be startling and disruptive to the user. An ordinary application that uses popup windows as part of its normal interaction can achieve much more pleasing results by using the VioReadCellStr and VioWrtCellStr functions to save and restore small portions of the display. Video Subsystems and Drivers The OS/2 substructure for the video support available to an application is organized in an atypical fashion (Figure 6-18). The character device driver SCREEN$, loaded from the file SCREEN01.SYS or SCREEN02.SYS, plays little role in the control of the display. Its main function is to return a selector for the physical video buffer; it has no documented DosDevIOCtl calls. Ŀ Kernel or Family application  Ŀ Vio calls DosWrite Ŀ VioWrtTTY Ŀ VIOCALLS.DLL Ĵ DOSCALL1.DLL Ŀ Private call interface Ŀ Ŀ ANSICALL.DLL DosDevIOCtl BVSCALLS.DLL  Ĵ IOPL segment Ŀ  OS/2 kernel  Request packet interface Ŀ SCREEN$ device driver Ŀ   Video adapter  Figure 6-18. The system modules responsible for video support. The dynlink library BVSCALLS.DLL is the Basic Video Subsystem that implements the Vio services. BVSCALLS contains the true driver for the video adapter; it manipulates the physical video buffer (using the selector obtained from SCREEN$). BVSCALLS contains an IOPL segment so that it can read and write the adapter's I/O ports to set the display mode, program the palette, and so forth. The dynlink library VIOCALLS.DLL is a "router." It contains the entry points for the Vio API and passes the calls onward to the appropriate subsystem (such as BVSCALLS) on a per-screen-group basis. Interpretation of ANSI escape sequences (when ANSI support is enabled) is carried out by ANSICALL.DLL. Although BVSCALLS is the default video subsystem for each screen group, part or all of its services can be replaced on a per-screen-group basis. To activate a new subsystem, call VioRegister with a module (dynlink library) name, an entry point name, and a set of flags that indicate which Vio services the module supports; those functions not present continue to be passed to BVSCALLS. VioDeRegister deactivates an alternative Vio subsystem and causes all Vio processing to rely on BVSCALLS. Chapter 7 Printer and Serial Ports OS/2 supplies drivers for parallel ports and serial ports that let you control printers, plotters, modems, and other devices for hard copy output or communication. Parallel ports are so named because they transfer a byte8 bitsin parallel to the destination device over 8 separate physical paths (plus additional status and handshaking signal wires). The serial port also communicates with the CPU using bytes, but it sends data to or receives data from its destination device seriallya bit at a timeover a single physical connection in each direction (plus a ground and optional handshaking signal wires). On personal computers, parallel ports are typically used for high-speed output devices, such as line printers, over relatively short distances (less than 50 feet). They are rarely used for devices that require two-way communication with the computer. Serial ports are used for lower-speed devices, such as modems and terminals that require two-way communication (although some printers also have serial interfaces). A serial port can drive a device reliably over much longer distances without extra hardware. The standard version of OS/2 includes drivers for three parallel adapters and for two serial adapters on the PC/AT (three on the PS/2). The logical names for these devices are LPT1 (which also has the alias PRN), LPT2, LPT3, COM1, COM2, and COM3. The device driver for the parallel ports, PRINT01.SYS or PRINT02.SYS, is always loaded during system initialization. The serial port driver, COM01.SYS or COM02.SYS, is loaded only if the user adds the appropriate DEVICE= statement to the CONFIG.SYS file in the root directory on the system boot disk. Sending Output to the Printer To send text and control codes to the printer, you must first call DosOpen with the logical device name of a printer (PRN, LPT1, LPT2, or LPT3); a handle is returned if the printer is available. You can use the deny-all sharing parameter for DosOpen to ensure that another process will not be permitted to use the printer at the same time. (This precaution is unnecessary if the print spooler is running.) Unlike DOS, OS/2 provides no predefined standard list device handle. When the printer is opened, DosWrite can be called as many times as necessary; a call to this function requires that you supply the handle, the address of data to be written, the length of the data (065,535 bytes), and the address of a variable (Figure 7-1). When DosWrite returns, it places the actual number of bytes written to the printer in the specified variable; this number is always the same as the number requested, barring some unexpected system error. Control characters and escape sequences are passed to the printer unfiltered and unchanged. (DosWrite always writes to character devices other than the standard output in binary mode.) pname db 'LPT1',0 ; device name phandle dw ? ; receives device handle action dw ? ; receives DosOpen action msg db 'HELLO' ; text of message msg_len equ $-msg ; length of message wlen dw ? ; receives bytes written . . . push ds ; device name address push offset DGROUP:pname push ds ; receives handle push offset DGROUP:phandle push ds ; receives DosOpen action push offset DGROUP:action push 0 ; filesize (N/A) push 0 push 0 ; attribute (N/A) push 1h ; flag: open, no create push 41h ; mode: write-only, deny-none push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . push phandle ; handle for LPT1 push ds ; message address push offset DGROUP:msg push msg_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 7-1. Obtaining a handle for the first list device (LPT1) with DosOpen, and then sending a message to the device with DosWrite. An application can initialize and configure the printer or obtain printer status with the Category 5 DosDevIOCtl functions (Figure 7-2). If your program reconfigures the printer, it should first obtain and save the printer's original configuration and then restore that status before terminating. Function Action Number 42H Sets frame control (chars/line, lines/inch) 44H Sets infinite retry 46H Initializes printer 48H Activates font 62H Gets frame control 64H Gets infinite retry 66H Gets printer status 69H Queries active font 6AH Verifies code page and font Figure 7-2. Category 5 DosDevIOCtl functions for control of the printer. The Print Spooler OS/2 includes a true print spooler that lets the user run multiple processes that generate printer output simultaneously. Using the print spooler is optional; to launch it in OS/2 1.0, issue a RUN directive in CONFIG.SYS or a START command. In OS/2 1.1, the spooler is installed as a default and can be disabled with the Control Panel. The spooler runs as a normal process in user mode and uses only documented API functions. When the print spooler is active, data that is logically written to the printer (from the application's point of view) might be buffered within the system for an unpredictable length of time. The spooler stores the output in a file at least until a DosClose is issued for the printer handle or until the process terminates, and then it adds the name of the file to its spool queue. The print spooler sends the contents of the file onward to the printer when the printer is on-line and when all previous files in the spool queue have been processed. If you are designing an application program, take a conservative approach with data that is "printed"; do not delete it from other storage unless it can be regenerated easilyin case the system crashes or is turned off before the spool queue is emptied. For the purposes of this book, the spooler is of interest mainly as an illustration of the power of the device monitor concept (see also Chapter 18). Under normal circumstances (when the print spooler is not loaded), data written by an application to the printer follows a simple and predictable path through the system (Figure 7-3). That is, the data is passed from the application to the OS/2 kernel by DosWrite calls, the kernel passes the data to the printer device driver in "write request packets," and the driver issues commands to the parallel port's hard-wired I/O addresses. When the print spooler is loaded, this simple data pathway changes. The spooler registers itself as a monitor with the printer driver and becomes an integral part of the driver's internal data stream. The driver collects the characters received from the OS/2 kernel into data packets that are passed through the buffers of all the monitors registered with the driver. The last monitor returns the packets to the driver, which then sends the data to the printer. Special packets are also sent to the monitors when the driver receives "open" and "close" commands from the kernel on behalf of an application. Ŀ Application DosWrite or DosDevIOCtl Ŀ DOSCALL1.DLL Private call interface Ŀ OS/2 kernel Request packet interface Ŀ Printer device driver Hardware-dependent I/O operations Ŀ Parallel port adapter Figure 7-3. The flow of printer data when the spooler is not loaded is straightforward. The DosWrite calls issued by an application are passed by DOSCALL1.DLL to the kernel. The kernel translates the DosWrite request into driver request packets, which are shipped to the printer driver. The driver processes the packets and transmits the data to the parallel port adapter. The job of the spooler is thus quite straightforward. When it sees an "open" packet, it creates a temporary spool file and consumes the subsequent data packets, writing the data to the file with ordinary DosWrite operations (Figure 7-4 on the following page). When the spooler sees a monitor "close" packet, it closes the spool file and adds the filename to its spool queue. When the spool file reaches the head of the queue, the spooler opens it, fetches its contents with DosRead, and stuffs the data back into the printer monitor chain; after traversing the buffers of any other monitors, the data arrives back at the printer driver and is written to the physical device. After the entire spool file is processed, the spooler deletes it. Ŀ Ŀ Application Spooler (Device monitor) DosWrite or DosDevIOCtl  Ŀ DOSCALL1.DLL Ŀ Private call interface Spool Ŀ file OS/2 kernel Request packet interface Ŀ Printer device driver  Hardware-dependent I/O operations Ŀ Parallel port adapter Figure 7-4. The flow of printer data when the spooler is loaded. The spooler is a process that registers as a device monitor with the printer driver. Data sent to the printer driver by the kernel passes through the chain of monitor buffers. The spooler removes the data from the chain and writes it into a temporary file. When the printer is available, the spooler reads the file back into memory and inserts the data into the monitor chain so that the driver can transfer it to the physical device. Serial Port Input and Output To use a serial port, an application must first call DosOpen with the logical device name of the desired port (COM1, COM2, or COM3). To claim exclusive use of a serial port and prevent interference by other processes, specify deny-all sharing mode in the DosOpen call. If the port is available, DosOpen returns a handle. As with the printer, OS/2 provides no predefined standard auxiliary device handle for the serial port that is equivalent to the handle supplied to applications under DOS. The general-purpose functions DosRead and DosWrite are used for input and output. They require familiar parametersa handle, the address and length of a data buffer, and a variable to receive the actual number of bytes read or written. Figure 7-5 offers an example. The driver services DosRead and DosWrite requests independently, in the order that they are received; transmit and receive operations can be (and frequently are) in progress simultaneously. cname db 'COM1',0 ; device name chandle dw ? ; receives handle action dw ? ; receives DosOpen action msg db 'HELLO' ; text of message msg_len equ $-msg ; length of message wlen dw ? ; receives bytes written . . . push ds ; device name address push offset DGROUP:cname push ds ; receives device handle push offset DGROUP:chandle push ds ; receives DosOpen action push offset DGROUP:action push 0 ; filesize (N/A) push 0 push 0 ; attribute (N/A) push 1h ; flag: open, no create push 12h ; mode: read/write, deny-all push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . push chandle ; handle for COM1 push ds ; message address push offset DGROUP:msg push msg_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 7-5. Obtaining a handle for COM1 with DosOpen, and then sending a message to it with DosWrite. The serial port driver is fully interrupt-driven and buffers characters internally. Thus, completion of a DosWrite operation (from the application's point of view) does not necessarily mean that the characters have been physically transmitted. The sizes of the driver's internal buffers are subject to change from version to version and may even (in a future version) be adjusted dynamically as a function of system activity. In OS/2 version 1.1, the receive buffer is 1024 bytes and the transmit buffer is 128 bytes. Handles for the serial port are always read and written in binary mode; ordinarily, control characters and escape sequences do not receive any special treatment and are passed through unchanged. If a process enables flow control, however, the XON and XOFF character codes are intercepted and processed by the driver. When the driver receives an XOFF from the serial port, it stops transmitting until it receives an XON code. Similarly, when its receive buffer is nearly full, the driver sends an XOFF to the serial port; when calls to DosRead from an application program empty the buffer to half-full or less, the driver sends an XON to the serial port so that the remote device can resume transmission. The default XON code is 11H (Ctrl-Q) and the default XOFF code is 13H (Ctrl-S), but your program can set each to another value. Configuring the Serial Port OS/2 provides protected mode applications with a comprehensive array of Category 1 DosDevIOCtl functions to control a number of options, among them the serial port baud rate, parity, character length, stop bits, timeout delays, and flow control (XON/XOFF processing). Functions are also available to check the serial port status and the configuration, the size of the serial port driver's receive and transmit buffers, and the number of characters waiting in those buffers. For those applications that must control modems or perform hardware handshaking, the following signals at the serial port connector can be controlled by DosDevIOCtl calls: Data Terminal Ready (DTR) Request to Send (RTS) Similarly, an application can use DosDevIOCtl calls to query the state of the following incoming signals at the serial port connector: Data Set Ready (DSR) Clear to Send (CTS) Data Carrier Detect (DCD) Ring Indicator (RI) The DosDevIOCtl functions for the serial port driver (Category 1) are summarized in Figure 7-6. Take care when reconfiguring the serial port "on the fly." The serial port driver processes DosDevIOCtl requests immediately upon receipt and does not attempt to serialize them with read and write requests; thus, the results can be unpredictable if the driver receives (for example) a DosDevIOCtl command to change the baud rate while it is in the process of receiving or transmitting data. The default configuration for each serial port, established during system initialization, is 1200 baud, 7-bit character length, even parity, and 1 stop bit. The DTR and RTS signals are initially off. They are turned on when the COM device is opened by an application, and they are turned off again when the COM device is closed and the transmit buffer is empty. Many other factors, however, can affect these signals during serial port communication. Function Action Number 41H Sets baud rate 42H Sets line characteristics (data bits, parity, stop bits) 44H Transmits byte immediate 45H Sets break off 46H Sets modem control signals (DTR, RTS) 47H Stops transmitting (as if XOFF received) 48H Starts transmitting (as if XON received) 4BH Sets break on 53H Sets device control block 61H Gets baud rate 62H Gets line characteristics (data bits, parity, stop bits, break status) 64H Gets COM status 65H Gets transmit data status 66H Gets modem output control signals (DTR, RTS) 67H Gets modem input control signals (DSR, CTS, DCD, RI) 68H Gets number of characters in receive buffer 69H Gets number of characters in transmit buffer 6DH Gets COM error information 72H Gets COM event information 73H Gets device control block Figure 7-6. Category 1 DosDevIOCtl functions for control of the serial port. To examine a more complete example of serial port programming under OS/2, see the DUMBTERM.C or DUMBTERM.ASM program in Chapter 12. This is a simple "dumb terminal" program that takes advantage of multiple threads to avoid the need for polling. DUMBTERM configures the serial port according to #define or equ statements in the source file; then it exchanges characters between the console (keyboard and video display) and the serial port until it receives an exit command. The Serial Port and Real Mode Applications The job of OS/2's serial port driver is complicated by the need to support the DOS compatibility environment. Because the DOS and ROM BIOS serial port drivers do not buffer data and are not interrupt-driven, most real mode applications control the serial port adapter directly and bypass operating system services completely. The driver uses some fairly simple strategies to arbitrate serial port usage between protected mode and real mode applications. It clears out the words in the ROM BIOS data area that normally contain the base port addresses of the serial adapters (location 0040:0000H for COM1, 0040:0002H for COM2). Consequently, real mode applications that test for the existence of the serial port by inspecting the ROM BIOS data area or by calling the ROM BIOS serial port driver will conclude that the adapter is not present. The OS/2 real mode command SETCOM40 updates the ROM BIOS data area to indicate the presence of a port. A real mode application can acquire a COM device in a number of ways: It can perform an Int 21H read, write, or IOCtl function call using the predefined handle (3) for the first serial port device, or it can explicitly open COM1, COM2, or COM3. Yet another method is to modify the interrupt vector for a serial port. OS/2 periodically checks the interrupt vector table; if it detects that the vector for a serial port has been changed, it assumes that the serial port has been claimed by the real mode application. When a real mode program is using a serial port, OS/2 fails all DosOpen requests by protected mode applications for that port until the real mode application terminates, closes the device, or restores the interrupt vector. If a protected mode program owns a COM port, and a real mode application attempts to open it or capture the interrupt vector, the user is notified by a critical error pop-up. The user can then elect to terminate the real mode application, ignore the error and continue, or (in the case of an attempted open) retry the operation. SECTION 3 PROGRAMMING MASS STORAGE Chapter 8 File Management Although the face of an application is the dexterity with which it manipulates the display, its heart is the set of routines that fetch, manipulate, and store data on media such as magnetic disks. The data on disk is organized into files, which are identified by name; files in turn can be organized by being grouped into directories; directories and files are stored on logical volumes. Applications concern themselves only with the names and contents of files; the details of a file's physical location on the disk and its retrieval are the province of the operating system. OS/2 provides applications with a variety of services to manipulate files, directories, and volumes in a hardware-independent manner. This chapter explains the OS/2 functions that create, open, close, rename, and delete disk files; read data from and write data to disk files; and inspect or change the information associated with filenames in disk directories (Figure 8-1 on the following page). Directories and volumes are covered in Chapter 9; the physical structure of OS/2 file systems is covered in Chapter 10. NOTE: The discussion of disk storage in this book centers on the so-called FAT (file allocation table) file systems, which are identical to those used by MS-DOS. Although OS/2 is designed to allow installation of other file systems as it evolves, the benefits of media compatibility with the vast installed base of MS-DOS machines guarantee that FAT file systems will prevail in OS/2 for a long time to come. Function Description File Operations DosBufReset Flushes file buffers to disk and updates directory DosClose Flushes file buffers to disk, updates directory entry, and releases handle DosDelete Removes a file from the directory DosDupHandle Returns a new handle that duplicates an existing handle, or forces one handle to track another DosMove Renames and/or moves a file (also renames directories) DosNewSize Truncates or extends an existing file DosOpen Opens, creates, or replaces a file, returning a handle DosSetMaxFH Sets maximum handles for process I/O Operations DosChgFilePtr Sets file pointer relative to start of file, current position, or end of file DosFileLocks Locks or unlocks file region DosRead Reads from file, device, or pipe (synchronous) DosReadAsync Reads from file, device, or pipe (asynchronous) DosWrite Writes to file, device, or pipe (synchronous) DosWriteAsync Writes to file, device, or pipe (asynchronous) File Information DosQFHandState Returns handle characteristics DosQFileInfo Returns file date, time, attributes, and size DosQFileMode Returns file attributes DosQHandType Returns file, pipe, or device code for a handle DosSetFHandState Sets handle characteristics DosSetFileInfo Sets file date and time DosSetFileMode Sets file attributes Figure 8-1. OS/2 file management services at a glance. Filenames and Handles To manipulate a file, a program must identify it to the operating system with a null-terminated (ASCIIZ) character string called a pathname. A pathname can contain four different elements in the following form: drive:path\filename.extension The drive code takes the form of a single letter followed by a colon (:) and identifies the logical volume that holds the file. The path identifies the directory that contains the filename; it can be a complete specification from the root directory of the volume, or it can be a relative specification (more about this in Chapter 9). If drive is absent, the current drive is assumed; if path is absent, the current directory is assumed. For each process, the system maintains a current drive and (for each drive) a current directory. The filename is the only component that must be present in a pathname. It contains a maximum of eight characters. The extension, when present, is really part of the filename; it appends a maximum of three characters, separated from the filename by a period. (These restrictions on filenames and extensions might not apply in non-FAT file systems.) Although extensions generally signify a file's "type" or contents, this is only a convention. Functions that operate on a file as a whole, such as those that delete or rename files, require only the ASCIIZ pathname. Functions that operate on the contents of files, however, refer to files with handles. A handle is a 16-bit tokenusually a small positive integerthat OS/2 provides to a program for its use in manipulating the associated file. (In a few moments, you'll see how a program obtains handles.) The value of a handle has no special significance, except that it is different from the values of handles for other files or devices being used at the same time by the same process. The default limit on the number of handles that a single process can own is 20. Three handles are preassigned to the standard input, standard output, and standard error devices (mentioned earlier in Chapters 5 and 6 in connection with the keyboard and video display). A process can inherit additional handles from its parent, further infringing on the initial allotment of 20. Fortunately, you can increase the per-process handle limit at run time with the function DosSetMaxFH. Creating, Opening, and Closing Files The API function DosOpen is the bridge between the functions that use pathnames and the functions that use handles. It accepts the pathname of a file to be created, opened, or replaced, and it returns a handle that can be used to manipulate the contents or attributes of the file. As we saw in Chapters 57, you can also use DosOpen to open character devices for input and output as though they were files. The DosOpen function is flexible; consequently, it requires many parameters and is the most complex file management function to master. Your program must supply DosOpen with the following information: The address of an ASCIIZ pathname for the file or device The OpenFlag and OpenMode parameters A file size A file attribute The addresses of two variables that will receive information from OS/2 The OpenFlag parameter has two fields that allow the DosOpen operation to be tailored to the previous existence or nonexistence of the file (Figure 8-2). For example, the combination "create if file does not exist" and "fail if file exists" amounts to a "test and lock" operation that can be used to implement semaphores in the form of files across a network. The OpenMode parameter controls a number of characteristics of the open file. It governs the process's access to the file, the manner of error handling and data buffering to be used, the direct disk access option (see Chapter 10), the level at which the file can be shared with other processes, and whether access to the file is inherited by child processes (Figure 8-3). If one or more other processes have already opened the same file, the outcome of the DosOpen operation depends on the requested access rights and sharing mode as shown in Figure 8-4 on p. 136. A process should ensure that its own DosOpen requests succeed in all appropriate conditions by specifying the minimum necessary access rights; for example, the process should not request read/write access when it only needs to read the file. Similarly, the process should avoid requesting restrictive sharing modes whenever possible. The DosOpen attribute and size parameters are relevant only when a file is being created or replaced. The attribute parameter marks the file as hidden, system, read-only, or modified since last backup (archive), as shown in Figure 8-5 on p. 136. The size parameter preallocates disk space for the file; the data in the preallocated area is undefined. (Later versions of OS/2 might attempt to preallocate contiguous disk space to improve file access times.) When an existing file is opened, the attribute and size parameters are ignored. F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ Action to take if file exists: 0000 = fail 0001 = open file 0010 = replace file Action to take if file does not exist 0000 = fail 0001 = create file Reserved Figure 8-2. OpenFlag parameter for the DosOpen function. F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ Access mode 000 = read-only 001 = write-only 010 = read/write Reserved Sharing mode 001 = deny-all 010 = deny-write 011 = deny-read 100 = deny-none Inheritance flag 0 = handle inherited by child processes 1 = handle not inherited Reserved Critical error mode 0 = reported by system pop-ups 1 = returned to process Write-through flag 0 = writes may be deferred 1 = snychronous physical writes required DASD access mode 0 = file access 1 = direct disk access Figure 8-3. OpenMode parameter for the DosOpen function. Use of DosOpen with the DASD bit set for direct disk access is discussed in Chapter 10. If the DosOpen operation succeeds, the function returns zero in register AX. The file handle, to be used for subsequent read, write, seek, and close operations, is returned in one of the variables. An "action" code is returned in the other variable: 0001 if the file existed and was opened, 0002 if the file was created, or 0003 if the file existed and was replaced (the previous data in the file was lost). If the call to DosOpen fails, an error code is returned in AX. The function call can fail for many reasons, including bad pathnames or other parameters, sharing conflicts with another process, inadequate access rights (to a network directory, for example), and exhaustion of process or system resources (such as handles). Ŀ Ŀ Requesting Other Process's Access Rights Process's Sharing Mode Read-only Write-only Read/Write ͻ ͻ ͻ Deny-all Fail Fail Fail ͼ ͼ ͼ Ŀ ͻ ͻ Deny-write Succeed Fail Fail ͼ ͼ ͻ Ŀ ͻ Deny-read Fail Succeed Fail ͼ ͼ Ŀ Ŀ Ŀ Deny-none Succeed Succeed Succeed Outcome of call to DosOpen Ŀ Ŀ Requesting Other Process's Sharing Mode Process's Access Rights Deny-all Deny-write Deny-read Deny-none ͻ Ŀ ͻ Ŀ Read-only Fail Succeed Fail Succeed ͼ ͼ ͻ ͻ Ŀ Ŀ Write-only Fail Fail Succeed Succeed ͼ ͼ ͻ ͻ ͻ Ŀ Read/Write Fail Fail Fail Succeed ͼ ͼ ͼ Outcome of call to DosOpen Figure 8-4. Outcome of DosOpen based on sharing mode and access rights of the requesting process and those of another process that previously opened the file. F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ Read-only Hidden System Reserved Archive Reserved Figure 8-5. Attribute parameter for the DosOpen function when it is used to create a file. Do not use the read-only attribute bit with DosOpen; you can apply it to the file later with DosSetFileMode. Also, do not use bits 3 and 4 with DosOpen; these bits, which indicate directories and volume labels in FAT file systems, are discussed further in Chapter 9. DosOpen is a good example of an OS/2 function call that is better suited to high level languages than to assembler. The large number of parameters that must be pushed on the stack bloat the source code (Figure 8-6). You might find it useful to encapsulate DosOpen within a subroutine that assumes reasonable default values for most parameters. If you also write the subroutine so that it places its local variables on the stack, multiple threads can share the subroutine without any need for semaphores or other synchronization mechanisms (Figure 8-7 on the following pages). fname db 'MYFILE.DAT',0 ; ASCIIZ pathname fhandle dw 0 ; receives handle faction dw 0 ; receives DosOpen action . . . push ds ; ASCIIZ pathname push offset DGROUP:fname push ds ; receives handle push offset DGROUP:fhandle push ds ; receives DosOpen action push offset DGROUP:faction push 0 ; file size (ignored) push 0 push 0 ; file attribute (ignored) push 1 ; flag: fail if file ; doesn't exist push 42h ; mode: deny-none, ; access read/write push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . Figure 8-6. Example of in-line code to open a file. When an existing file is opened, the attribute and size parameters are ignored. Compare with Figure 8-7. ; FOPEN: reentrant routine to open a file or device ; ; Call with: DS:DX = address of ASCIIZ pathname ; AX = OpenMode ; Returns: Z = true if successful ; BX = handle ; AX = DosOpen action ; or ; Z = false if failed ; AX = DosOpen error code ; Uses: DX fhandle equ [bp-2] faction equ [bp-4] fopen proc near push bp ; save original BP value mov bp,sp ; set up stack frame sub sp,4 ; allocate local variables push ds ; ASCIIZ pathname push dx push ss ; receives handle lea dx,fhandle push dx push ss ; receives DosOpen action lea dx,faction push dx push 0 ; file size (ignored) push 0 push 0 ; file attribute (ignored) push 1 ; flag: fail if file ; doesn't exist push ax ; mode: from caller push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; set Z flag to indicate jnz fopen1 ; success or failure mov bx,fhandle ; get handle mov ax,faction ; get DosOpen action fopen1: mov sp,bp ; discard local variables pop bp ; restore original BP ret ; back to caller fopen endp Figure 8-7. Example of a reentrant procedure to encapsulate the job of opening a file. Local variables are created on the stack to receive the results of the DosOpen operation. Compare with Figure 8-6. Once a file has been opened or created, a process can use several auxiliary functions to obtain or modify information associated with the file. DosQHandType returns information indicating whether a handle is associated with a file, a pipe, or a character device; DosQFileMode returns a file's attributes; DosQFileInfo returns a file's size, attributes, and date and time stamps; and DosQFHandState returns the write-through, inheritance, sharing, and access rights associated with a file handle. These functions are especially useful to a child process that has inherited a handle and needs to know how that handle may be used. A similar set of functions permits a process to change attributes, access rights, and the like "on the fly." When a process finishes using a file, it calls DosClose to release the handle, flush to disk any unwritten data that is buffered within OS/2, and update the size, date, and time in the directory entry for the file if it has been modified. Under normal circumstances, a DosClose operation fails only if the file was on a floppy disk, the file was modified, and the user removed the disk while the file was open. Reading and Writing Files After a process opens or creates a file, it is ready to do useful work: fetching and displaying information from the file, modifying the data already stored in the file, or adding new data to the file. Let's review some key terms related to file access: Reading and writing The file pointer Sequential and random access Synchronous and asynchronous access Reading is copying data from disk into RAM; the data on the disk is unchanged, and the previous contents of the area of RAM are lost. Writing is copying data from RAM onto disk; the contents of RAM are unchanged, and the previous contents of the disk area are lost. Data is not necessarily physically transferred between the disk and RAM when an application's read or write request is "logically" complete; OS/2 often buffers disk data internally for extended periods to improve performance. OS/2 maintains a file pointer for each handle in the system file table (SFT). When an application issues a read or write request, the current value of the file pointer for that handle determines the byte offset (from the start of the file) at which the transfer will begin. When the transfer is complete, OS/2 updates the file pointer so that it points one byte past the last byte that was read or written. An application can also set the file pointer to an arbitrary valuethis is often referred to as a seek operation (not to be confused with the physical seek performed by a disk drive, which involves moving the read/write heads to a specific cylinder). Sequential access is reading or writing consecutive data in a file. This type of processing is typically used for accessing files with variable-length records; a text file, in which each line can be considered a record delimited by a newline character, is an example of such a file. A program that processes files sequentially need not concern itself with the file pointer because OS/2 maintains it. Random access is reading and writing nonsequential recordsthe physical ordering of the records in the file does not determine the order in which they are processed. (The term is not particularly apt, of course, because the processing is not at all random.) In simple applications, random access is typically used for files that contain fixed-length records. A program can calculate the location of a record (by multiplying a record number times the record size), set the file pointer to that offset, and then request a read or write. The indexed file access methods used by more sophisticated applications are much more versatile. In the classic definition of synchronous access, the process requesting the read or write operation is suspended until the transfer is complete. On the other hand, when asynchronous access is requested, the operating system starts the transfer and then returns control to the process, notifying the process by some other means when the read or write operation is finished. With this terminology in hand, let's take a look at OS/2's five fundamental services for reading and writing files: DosRead, DosReadAsync, DosWrite, DosWriteAsync, and DosChgFilePtr. Figure 8-8 contains a sample code skeleton for file access by an OS/2 application. recsize equ 1024 ; file record size fname1 db 'OLDFILE.DAT',0 ; input filename fname2 db 'NEWFILE.DAT',0 ; output filename fhandl1 dw 0 ; input file handle fhandl2 dw 0 ; output file handle faction dw 0 ; receives DosOpen action rlen dw 0 ; length from DosRead wlen dw 0 ; length from DosWrite buffer db recsize dup (?) ; buffer for file I/O . . . ; open input file... push ds ; input filename push offset DGROUP:fname1 push ds ; receives handle push offset DGROUP:fhandl1 push ds ; receives DosOpen action push offset DGROUP:faction push 0 ; file size (ignored) push 0 push 0 ; file attribute (ignored) push 1 ; flag: fail if file ; doesn't exist push 40h ; mode: deny-none, ; access read-only push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; was open successful? jnz error ; jump if function failed . . . ; create output file... push ds ; output filename push offset DGROUP:fname2 push ds ; receives handle push offset DGROUP:fhandl2 push ds ; receives DosOpen action push offset DGROUP:faction push 0 ; initial file size = 0 push 0 push 0 ; attribute = normal push 12 ; flag: create or replace push 41h ; mode: deny-none, ; access write-only push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; was create successful? jnz error ; jump if function failed next: ; read next record from ; input file... push fhandl1 ; input file handle push ds ; buffer address push offset DGROUP:buffer push recsize ; length to read push ds ; receives actual length push offset DGROUP:rlen call DosRead ; transfer to OS/2 or ax,ax ; was read successful? jnz error ; jump if function failed cmp rlen,0 ; read length = 0? je done ; jump if end of file . ; other processing of record . ; performed here... . ; write processed record ; to output file... push fhandl2 ; output file handle push ds ; buffer address push offset DGROUP:buffer push rlen ; write length = read length push ds ; receives actual length push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; was write successful? jnz error ; jump if function failed mov ax,rlen ; was write complete? cmp ax,wlen jne error ; jump if disk full jmp next ; process another record done: ; end of file reached... push fhandl1 ; close input file call DosClose ; transfer to OS/2 push fhandl2 ; close output file call DosClose ; transfer to OS/2 ; now terminate process... push 1 ; terminate all threads push 0 ; return code = 0 (success) call DosExit ; transfer to OS/2 Figure 8-8. Skeleton code for an OS/2 assembly language program that performs synchronous, sequential file access. In this simple example, partial records at end of file do not receive any special treatment, and the error status returned by DosClose is ignored. DosRead and DosWrite perform synchronous reads and writes. Both functions require a handle, the address of the data to be written or the buffer to receive the data to be read, a length, and the address of a variable. When the functions return, the variable contains the number of bytes actually transferred. In the case of files, the number of bytes transferred might be less than the number requested for two reasons: The data was being written to disk, and the disk was full; or the data was being read from disk, and the end of the file was reached. Neither of these events is considered to be an error; that is, zero is returned in AX for both. The functions DosReadAsync and DosWriteAsync provide asynchronous reads and writes. These functions require the same parameters as DosRead and DosWrite; in addition, they require addresses for a RAM semaphore and a variable to receive an error code. The program sets the semaphore before calling DosReadAsync or DosWriteAsync; when the requested operation is complete, the semaphore is cleared, and an error code and number of bytes transferred are returned in variables. (Chapter 13 provides more information about semaphores.) OS/2's support for threads provided a simple means of implementing DosReadAsync and DosWriteAsync. These functions merely create a new thread on behalf of the calling process that executescarrying out the I/O, clearing the semaphore, and then destroying itselfwhile the original thread continues its execution. You can generally get better performance in your application by using multiple threads explicitly, rather than letting them be created and destroyed in this hidden manner. DosChgFilePtr allows a process to set the file pointer associated with a handle to an arbitrary offset. The program supplies a code that indicates the "method" in which the offset is specified: relative to the start of file (method 0), to the current file pointer (method 1), or to the end of file (method 2). When methods 1 and 2 are used, the offset can be either positive or negative. In any case, the function returns the resulting absolute offset from the start of file. There is little distinction between sequential and random access under OS/2. When a file is opened or created, its file pointer is initially set to zero. If a process simply issues reads or writes without ever calling DosChgFilePtr, then its access to the file is sequential. If a process requires random access, it need only call DosChgFilePtr before each read or write to position the file pointer appropriately. Naturally, these techniques can be used together; a process can seek to a specific record and then process sequential records by consecutive read or write operations. Note the following special uses of DosChgFilePtr: To obtain the current position of the file pointer without modifying it, call the function with method 1 and an offset of zero; to find the file size, call DosChgFilePtr with method 2 and an offset of zero. WARNING: When multiple threads use the same file handle, they must do so cooperatively because OS/2 does not attempt to serialize requests from different threads or protect one thread from another. For example, if a thread calls DosChgFilePtr followed by DosRead to read a specific recordand another thread's request using the same handle gets serviced in betweenthe file pointer will be changed, and the first thread will get the wrong data. The order in which requests from different threads are completed depends on the priorities of the threads and on the physical location of the data on the disk as well as on the order of the requests themselves. Forcing Disk Updates OS/2's ability to buffer data internally to eliminate unnecessary reads or defer writes improves system throughput. However, some programs must be able to bring a fileon demandto a consistent state on the physical disk, in case power fails, the system crashes, or the process terminates unexpectedly. Imagine, for example, the potential problems if the index to a database was updated but the corresponding record was never written, or vice versa. The function DosBufReset writes the system's internal buffers associated with a specific file handle to the disk and updates the corresponding entry in the disk directory; the original handle remains open for further use. If DosBufReset is called with a handle of -1, the buffers are flushed and the directory entries updated for all of the process's open files. If any files are on removable media and those media were changed, OS/2 prompts the user to mount the necessary disks. The methods used in MS-DOS programming to force a disk updateclosing and reopening a handle or duplicating and closing a handleare inefficient and unnecessary in OS/2. File and Record Locking In a multitasking, network-oriented environment such as OS/2, it is to be expected that the user will (intentionally or unintentionally) attempt to use the same file with several programs at once, or open a file on a network server that is also in use by a program running in another network node. If programs do not defend themselves against the user by proper use of OS/2's sharing and locking facilities, deadlocks or race conditions can occur, and data can be lost or corrupted. A deadlock occurs when two processes are each waiting for the other. (For example, Process A has opened file X with deny-all sharing mode and needs file Y, and Process B has opened file Y with deny-all and needs file X.) A race condition occurs when two process are modifying shared data without cooperating; the final state of the data depends on which process runs when. You can restrict access by other processes to an entire file by using the proper sharing mode in a DosOpen call. After a process successfully opens a file with a sharing mode of deny-all or deny-write, it can modify the file with impunity. Such a process is not regarded as "friendly" in a multitasking environment, however, because it interferes with other processes' access to the file for a prolonged period. A better approach is to open the file with a sharing mode of deny-none and then to exclude other processes from access to specific file regions only during updates of those regions. The function DosFileLocks allows a process to lock, or request exclusive access to, a file region of arbitrary position and size. Another locked region can be unlocked during the same function call. While a lock is in effect, calls issued by other processes to read, write, or lock the same file region will fail. If a process cannot lock a file region, it can delay and retry the lock operation, or it can display a message so that the user can resolve the contention. The system recognizes locks on file regions on a per-process basis. Thus, child processes do not inherit region locks, although they do inherit overall sharing modes and access rights for a file. Also, a DosFileLocks call does not protect one thread against interference by another using the same handle; such protection must be obtained by other means. Renaming and Deleting Files Use DosMove to rename a file or move it to another directory of the same drive (or to do both at once). The function requires two ASCIIZ pathnames for the old and new locations and names of the file; it returns an error if the file is currently opened by the same or another process, if some element of the old or new directory path does not exist, or if a file by the new name already exists in the destination directory. In contrast to the user-level RENAME command, DosMove works on one file at a time and does not accept wildcard characters in filenames. The function DosDelete removes a file from the disk directory. It fails if the file is read-only or is currently opened by the same or another process. Unlike the DEL and ERASE user commands supported by CMD.EXE and COMMAND.COM, DosDelete does not tolerate pathnames that contain wildcards and only deletes one file at a time. When you delete a file in FAT file systems, its directory entry is marked as available by a change in the first byte of the entry (to a reserved value). The disk sectors previously allocated to the file are released. The remainder of the directory entry and the actual contents of the disk sectors are not altered, making it possible for "un-erase" programs to reclaim the file under some circumstances. A program that requires a higher degree of security should overwrite the entire file with zeros or other data before it deletes the file. Sample Programs: DUMP.ASM and DUMP.C The listings DUMP.C (Figure 8-9) and DUMP.ASM (Figure 8-10) are the source code for DUMP.EXE, a utility program that displays the binary contents of a file in hex and ASCII. These listings illustrate many of the basic file management techniques you will need in your own application programs. The two programs are symmetric and produce identical output (an example of which is shown in Figure 8-14 on p. 163). /* DUMP.C Displays the binary contents of a file in hex and ASCII on the standard output device. Demonstrates direct calls to OS/2 API from a C program. Compile with: C> cl dump.c Usage is: C> dump pathname.ext [>destination] Copyright (C) 1987 Ray Duncan */ #include #define RECSIZE 16 /* size of file records */ #define API unsigned extern far pascal API DosClose(unsigned); /* function prototypes */ API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long, unsigned, unsigned, unsigned, unsigned long); API DosRead(unsigned, void far *, unsigned, unsigned far *); main(int argc, char *argv[]) { char file_buf[RECSIZE]; /* data block from file */ unsigned long foffset = 0L; /* file offset in bytes */ unsigned handle; /* DosOpen variable */ unsigned action, length; /* DosRead variables */ unsigned flag = 0x01; /* fail if file not found */ unsigned mode = 0x40; /* read-only, deny-none */ if(argc < 2) /* check command tail */ { fprintf(stderr, "\ndump: missing filename\n"); exit(1); } /* open file or exit */ if(DosOpen(argv[1], &handle, &action, 0L, 0, flag, mode, 0L)) { fprintf(stderr, "\ndump: can't find file %s\n", argv[1]); exit(1); } /* read and dump records */ while((DosRead(handle, file_buf, RECSIZE, &length) == 0) && (length != 0)) { dump_rec(file_buf, foffset, length); foffset += RECSIZE; } printf("\n"); /* extra blank line */ DosClose(handle); /* close the input file */ exit(0); /* return success code */ } /* Display record (16 bytes) in hex and ASCII on standard output. */ dump_rec(char *buffer, long foffset, int length) { int i; /* index to current record */ if(foffset % 128 == 0) /* maybe print heading */ printf("\n\n 0 1 2 3 4 5 6 7 8 9 A B C D E F"); printf("\n%04lX ", foffset); /* file offset */ for(i = 0; i < length; i++) /* print hex equivalent of each byte */ printf(" %02X", (unsigned char) buffer[i]); if(length != 16) /* space over if last record */ for(i=0; i<(16-length); i++) printf(" "); printf(" "); for(i = 0; i < length; i++) /* print ASCII equiv. of bytes */ { if(buffer[i] < 32 || buffer[i] > 126) putchar('.'); else putchar(buffer[i]); } } Figure 8-9. DUMP.C, the C source code for DUMP.EXE. title DUMP -- Display File Contents page 55,132 .286 ; ; DUMP.ASM ; ; Displays the binary contents of a file in hex and ASCII on the ; standard output device. ; ; Assemble with: C> masm dump.asm; ; Link with: C> link dump,,,os2,dump ; ; Usage is: C> dump pathname.ext [>destination] ; ; Copyright (C) 1988 Ray Duncan ; cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII line feed blank equ 20h ; ASCII space code blksize equ 16 ; size of input file records stdout equ 1 ; standard output handle stderr equ 2 ; standard error handle extrn DosOpen:far extrn DosRead:far extrn DosWrite:far extrn DosClose:far extrn DosExit:far extrn argc:near ; returns argument count extrn argv:near ; returns argument pointer DGROUP group _DATA _DATA segment word public 'DATA' fname db 64 dup (0) ; name of input file fhandle dw 0 ; input file handle faction dw 0 ; action from DosOpen fptr dw 0 ; relative file address rlen dw 0 ; actual number of bytes ; read by DosRead wlen dw 0 ; actual number of bytes ; written by DosWrite output db 'nnnn',blank,blank ; output format area outputa db 16 dup ('nn',blank) db blank outputb db 16 dup (blank),cr,lf output_len equ $-output hdg db cr,lf db 7 dup (blank) db '0 1 2 3 4 5 6 7 ' db '8 9 A B C D E F',cr,lf hdg_len equ $-hdg fbuff db blksize dup (?) ; data from file msg1 db cr,lf db 'dump: file not found' db cr,lf msg1_len equ $-msg1 msg2 db cr,lf db 'dump: missing filename' db cr,lf msg2_len equ $-msg2 msg3 db cr,lf db 'dump: empty file' db cr,lf msg3_len equ $-msg3 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP dump proc far ; entry point from OS/2 push ds ; make DGROUP addressable pop es ; via ES call argc ; is filename present cmp ax,2 ; in command tail? je dump1 ; yes, proceed mov dx,offset msg2 ; missing or illegal filespec, mov cx,msg2_len jmp dump9 ; display error message and exit dump1: ; copy filename from command ; tail to local buffer mov ax,1 ; get pointer to command tail call argv ; argument in ES:BX mov cx,ax ; CX = filename length mov di,offset fname ; DS:DI = local buffer dump2: mov al,es:[bx] ; copy filename byte by byte mov [di],al inc bx inc di loop dump2 push ds ; restore ES = DGROUP pop es dump4: ; try to open file... push ds ; address of filename push offset fname push ds ; receives file handle push offset fhandle push ds push offset faction ; receives DosOpen action push 0 ; file size (ignored) push 0 push 0 ; file attribute (ignored) push 1 ; OpenFlag: ; fail if file doesn't exist push 40h ; OpenMode: ; deny-none, access = read-only push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; was open successful? jz dump5 ; yes, proceed mov dx,offset msg1 ; open failed, display mov cx,msg1_len ; error message and exit jmp dump9 dump5: call rdblk ; initialize file buffer cmp rlen,0 ; anything read? jne dump6 ; jump, got some data cmp fptr,0 ; no data, was this first read? jne dump8 ; no, end of file reached mov dx,offset msg3 ; empty file, print error mov cx,msg3_len ; message and exit jmp dump9 dump6: test fptr,07fh ; time for a heading? jnz dump7 ; no, jump ; write heading... push stdout ; standard output handle push ds ; address of heading push offset hdg push hdg_len ; length of heading push ds ; receives bytes written push offset wlen call DosWrite dump7: call cnvblk ; convert one block of ; binary data to ASCII ; write formatted output... push stdout ; standard output handle push ds ; address of output push offset output push output_len ; length of output push ds ; receives bytes written push offset wlen call DosWrite jmp dump5 ; get more data dump8: ; end of file reached... push fhandle ; close input file call DosClose ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 0 ; return code = 0 (success) call DosExit ; transfer to OS/2 dump9: ; print error message on ; standard error device push stderr push ds ; address of message push dx push cx ; length of message push ds ; receives bytes written push offset wlen call DosWrite ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 1 ; return code = 1 (error) call DosExit ; transfer to OS/2 dump endp ; RDBLK: Read block of data from input file ; ; Call with: nothing ; Returns: AX = error code (0 = no error) ; Uses: nothing rdblk proc near push fhandle ; input file handle push ds ; buffer address push offset fbuff push blksize ; buffer length push ds ; receives bytes read push offset rlen call DosRead ; transfer to OS/2 ret ; back to caller rdblk endp ; CNVBLK: Format one binary record for output ; ; Call with: nothing ; Returns: nothing ; Uses: AX, BX, CX, DX, DI cnvblk proc near mov di,offset output ; clear output format mov cx,output_len-2 ; area to blanks mov al,blank rep stosb mov di,offset output ; convert current file mov ax,fptr ; offset to ASCII call wtoa xor bx,bx ; point to start of data cb1: mov al,[fbuff+bx] ; get next byte of data ; from input file lea di,[bx+outputb] ; calculate output address ; for ASCII equivalent mov byte ptr [di],'.' ; if control character, cmp al,blank ; substitute a period jb cb2 ; jump, not alphanumeric cmp al,7eh ja cb2 ; jump, not alphanumeric mov [di],al ; store ASCII character cb2: ; now convert byte to hex mov di,bx ; calculate output address imul di,di,3 ; (position*3) + base address add di,offset outputa call btoa ; convert data byte to hex inc bx ; advance through record cmp bx,rlen ; entire buffer converted? jne cb1 ; no, get another byte add fptr,blksize ; update file offset ret ; back to caller cnvblk endp ; WTOA: Convert word to hex ASCII ; ; Call with: AX = data to convert ; ES:DI = storage address ; Returns: nothing ; Uses: AX, CL, DI wtoa proc near push ax ; save original value mov al,ah call btoa ; convert upper byte pop ax ; restore original value call btoa ; convert lower byte ret ; back to caller wtoa endp ; BTOA: Convert byte to hex ASCII ; ; Call with: AL = data to convert ; ES:DI = storage address ; Returns: nothing ; Uses: AX, CL, DI btoa proc near sub ah,ah ; clear upper byte mov cl,16 ; divide by 16 div cl call ascii ; convert quotient stosb ; store ASCII character mov al,ah call ascii ; convert remainder stosb ; store ASCII character ret ; back to caller btoa endp ; ASCII: Convert nibble to hex ASCII ; ; Call with: AL = data to convert in low 4 bits ; Returns: AL = ASCII character ; Uses: nothing ascii proc near add al,'0' ; add base ASCII value cmp al,'9' ; is it in range 0-9? jle ascii2 ; jump if it is add al,'A'-'9'-1 ; no, adjust for range A-F ascii2: ret ; return ASCII character in AL ascii endp _TEXT ends end dump Figure 8-10. DUMP.ASM, the MASM source code for DUMP.EXE. Figures 8-11 and 8-12 provide the source code for the subroutines ARGV and ARGC. DUMP parses the pathname for the file to be displayed from the command tail at the end of the environment. It sends the formatted dump to the standard output device; you can redirect the output to a file, another character device, or another process through a pipe. Error messages are sent to the standard error device so that they will not be affected by redirection of the standard output. The C version of DUMP demonstrates calls to the OS/2 API from a high level language. Calling OS/2 functions directly for file management, instead of using the C runtime library, makes the program smaller and faster (but less portable). The MASM version of DUMP works in much the same way as the C version but yields an EXE file that is only one-fifth as large. DUMP.ASM uses two external subroutines, ARGC.ASM (Figure 8-11) and ARGV.ASM (Figure 8-12), which you will find helpful in just about any MASM program and which will be used again several times in this book. ARGC returns the number of command line arguments, and ARGV returns the address and length of a particular command line argument. title ARGC -- Return Argument Count page 55,132 .286 ; ; ARGC.ASM ; ; Return count of command line arguments. Treats blanks and ; tabs as whitespace. ; ; Assemble with: C> masm argc.asm; ; ; Call with: N/A ; ; Returns: AX = argument count (always >= 1) ; ; Uses: nothing (other registers preserved) ; ; Copyright (C) 1987 Ray Duncan ; tab equ 09h ; ASCII tab blank equ 20h ; ASCII space character extrn DosGetEnv:far _TEXT segment word public 'CODE' assume cs:_TEXT public argc ; make ARGC available to Linker ; local variables envseg equ [bp-2] ; environment segment cmdoffs equ [bp-4] ; command line offset argc proc near enter 4,0 ; make room for local variables push es ; save original ES, BX, and CX push bx push cx push ss ; get selector for environment lea ax,envseg ; and offset of command line push ax push ss lea ax,cmdoffs push ax call DosGetEnv ; transfer to OS/2 or ax,ax ; check operation status mov ax,1 ; force argc >= 1 jnz argc3 ; inexplicable failure mov es,envseg ; set ES:BX = command line mov bx,cmdoffs argc0: inc bx ; ignore useless first field cmp byte ptr es:[bx],0 jne argc0 argc1: mov cx,-1 ; set flag = outside argument argc2: inc bx ; point to next character cmp byte ptr es:[bx],0 je argc3 ; exit if null byte cmp byte ptr es:[bx],blank je argc1 ; outside argument if ASCII blank cmp byte ptr es:[bx],tab je argc1 ; outside argument if ASCII tab ; otherwise not blank or tab, jcxz argc2 ; jump if already inside argument inc ax ; else found argument, count it not cx ; set flag = inside argument jmp argc2 ; and look at next character argc3: pop cx ; restore original BX, CX, ES pop bx pop es leave ; discard local variables ret ; return AX = argument count argc endp _TEXT ends end Figure 8-11. ARGC.ASM, the MASM source code for the ARGC routine called by the DUMP utility. This procedure returns the number of command line arguments. title ARGV -- Return Argument Pointer page 55,132 .286 ; ; ARGV.ASM ; ; Return address and length of specified command line argument ; or fully qualified program name. Treats blanks and tabs as ; whitespace. ; ; Assemble with: C> masm argv.asm; ; ; Call with: AX = argument number (0 based) ; ; Returns: ES:BX = argument address ; AX = argument length (0 = no argument) ; ; Uses: nothing (other registers preserved) ; ; Note: if called with AX = 0 (argv[0]), returns ES:BX ; pointing to fully qualified program name in environment ; block and AX = length. ; ; Copyright (C) 1987 Ray Duncan ; tab equ 09h ; ASCII tab blank equ 20h ; ASCII space character extrn DosGetEnv:far _TEXT segment word public 'CODE' assume cs:_TEXT public argv ; make ARGV available to Linker ; local variables... envseg equ [bp-2] ; environment segment cmdoffs equ [bp-4] ; command line offset argv proc near enter 4,0 ; make room for local variables push cx ; save original CX and DI push di push ax ; save argument number push ss ; get selector for environment lea ax,envseg ; and offset of command line push ax push ss lea ax,cmdoffs push ax call DosGetEnv ; transfer to OS/2 or ax,ax ; test operation status pop ax ; restore argument number jnz argv7 ; jump if DosGetEnv failed mov es,envseg ; set ES:BX = command line mov bx,cmdoffs or ax,ax ; is requested argument = 0? jz argv8 ; yes, jump to get program name argv0: inc bx ; scan off first field cmp byte ptr es:[bx],0 jne argv0 xor ah,ah ; initialize argument counter argv1: mov cx,-1 ; set flag = outside argument argv2: inc bx ; point to next character cmp byte ptr es:[bx],0 je argv7 ; exit if null byte cmp byte ptr es:[bx],blank je argv1 ; outside argument if ASCII blank cmp byte ptr es:[bx],tab je argv1 ; outside argument if ASCII tab ; if not blank or tab... jcxz argv2 ; jump if already inside argument inc ah ; else count arguments found cmp ah,al ; is this the one we're looking for? je argv4 ; yes, go find its length not cx ; no, set flag = inside argument jmp argv2 ; and look at next character argv4: ; found desired argument, now ; determine its length... mov ax,bx ; save parameter starting address argv5: inc bx ; point to next character cmp byte ptr es:[bx],0 je argv6 ; found end if null byte cmp byte ptr es:[bx],blank je argv6 ; found end if ASCII blank cmp byte ptr es:[bx],tab jne argv5 ; found end if ASCII tab argv6: xchg bx,ax ; set ES:BX = argument address sub ax,bx ; and AX = argument length jmp argvx ; return to caller argv7: xor ax,ax ; set AX = 0, argument not found jmp argvx ; return to caller argv8: ; special handling for argv = 0 xor di,di ; find the program name by xor al,al ; first skipping over all the mov cx,-1 ; environment variables... cld argv9: repne scasb ; scan for double null (can't use scasb ; SCASW since might be odd address) jne argv9 ; loop if it was a single null mov bx,di ; save program name address mov cx,-1 ; now find its length... repne scasb ; scan for another null byte not cx ; convert CX to length dec cx mov ax,cx ; return length in AX argvx: ; common exit point pop di ; restore original CX and DI pop cx leave ; discard stack frame ret ; return to caller argv endp _TEXT ends end Figure 8-12. ARGV.ASM, the MASM source code for the ARGV routine called by the DUMP utility. This procedure returns the address and length of a specific command line argument. To compile DUMP.C into the relocatable object module DUMP.OBJ and then link DUMP.OBJ and the DUMP.DEF file (Figure 8-13) into the executable module DUMP.EXE, use the following command: [C:\] CL DUMP.C To assemble and link the components of the MASM version of DUMP, collect the source files DUMP.ASM, ARGC.ASM, and ARGV.ASM and the module definition file DUMP.DEF (Figure 8-13). Then enter the following sequence of commands: [C:\] MASM DUMP.ASM; [C:\] MASM ARGC.ASM; [C:\] MASM ARGV.ASM; [C:\] LINK DUMP+ARGC+ARGV,,,OS2,DUMP NAME DUMP WINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 8-13. DUMP.DEF, the module definition file for DUMP.EXE. The DUMP program uses only those API functions which have counterparts in MS-DOS, so DUMP is eligible to be converted to a Family application that can run in both real mode and protected mode. To make this conversion, remove the PROTMODE statement from the DUMP.DEF file, rebuild DUMP.EXE, and then bind the program with the following command: [C:\] BIND DUMP.EXE OS2.LIB API.LIB The resulting DUMP.EXE file can be executed in either real mode or protected mode on an 80286- or 80386-based machine. Because the program uses 80286-specific instructions (such as PUSH "immediate," ENTER, and LEAVE), you cannot use it on an 8086/88-based machine without modifying the source code. 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 80 06 00 04 44 55 4D 50 40 96 1F 00 00 04 43 4F ....DUMP@.....CO 0010 44 45 04 44 41 54 41 06 44 47 52 4F 55 50 05 5F DE.DATA.DGROUP._ 0020 44 41 54 41 05 5F 54 45 58 54 10 98 07 00 68 86 DATA._TEXT....h. 0030 01 06 02 01 69 98 07 00 48 1F 01 05 03 01 F0 9A ....i...H....... 0040 04 00 04 FF 02 5D 8C 30 00 08 44 4F 53 43 4C 4F .....].0..DOSCLO 0050 53 45 00 07 44 4F 53 45 58 49 54 00 07 44 4F 53 SE..DOSEXIT..DOS 0060 4F 50 45 4E 00 07 44 4F 53 52 45 41 44 00 08 44 OPEN..DOSREAD..D 0070 4F 53 57 52 49 54 45 00 18 A0 8A 01 01 00 00 1E OSWRITE......... Figure 8-14. Example output from DUMP.EXE. Chapter 9 Volumes and Directories Each file in an OS/2 system is uniquely identified by name and location. The location, in turn, has two components: a path, which identifies the directory where the filename may be found, and the logical drive on which the directory (and hence the file) resides. The drive and directory may be implicit or explicit; during a process's execution, it has a "current drive" and a "current directory" for each drive, all of which are directly controlled by the process. Logical drives are specified by a single letter (AZ or az) followed by a colon. Drive letters are always assigned starting with A; the highest drive identifier in the system depends on its configuration (number of floppy disk drives, fixed drives, RAM disks, CD-ROMs, and so forth). The number of logical drives in a system may well not be the same as the number of physical drives; for example, large fixed disk drives are commonly broken up into two or more logical drives to make them easier to manage and back up. The key aspect of a logical drive is that it contains a self-consistent and self-sufficientfile system. That is, it contains one or more directories, zero or more complete files, and all the information needed to locate the files and directories and to determine which disk space is free and which is already in use. A drive's directories are simply little lists or catalogs. Each entry in a directory consists of the name, size, starting location, and attributes of a file or another directory that the drive contains as well as the date and time at which the file or directory was last modified. The detailed information about the location of every block of data assigned to a file or directory is kept in a separate control area on the disk (see Chapter 10). Each logical drive has a root directory, which is distinct from all the directories created within it. The root directory is always present and has a maximum number of entries; its size is determined when you format the disk and cannot be changed. Subdirectories of the root directory, which may or may not be present on a given disk, can be nested to any level and can grow to any size (until the space on the disk is exhausted). This organizational scheme is known as a hierarchical, or tree, directory structure (Figure 9-1). The user can interactively examine, select, create, or delete directories with the DIR, CHDIR (CD), MKDIR (MD), or RMDIR (RD) commands. Application programs can carry out similar operations using API calls (summarized in Figure 9-2) to do the following: Search for file or directory entries within directories Obtain or change the current drive or directory for a process Create or delete directories Rename directories (other than the root directory) Obtain, add, or change a volume label Ŀ Drive identifier Ŀ Root directory (Volume label) Ŀ Ŀ Ŀ Ŀ Ŀ Ŀ Ŀ File A Directory File B Directory File C Ŀ Ŀ Ŀ Ŀ Ŀ Directory File D File E File F Figure 9-1. The structure of an OS/2 FAT (MS-DOScompatible) file system. Function Description Directory Search Operations DosFindFirst Finds first matching file; returns a search handle DosFindNext Finds next matching file using search handle DosFindClose Closes a search handle DosSearchPath Searches list of directories for file Directory Operations DosChDir Selects current directory DosMkDir Creates a directory DosRmDir Removes a directory DosMove Renames a directory (also renames or moves files) DosQCurDir Returns current directory DosQSysInfo Returns maximum path length Drive Operations DosQCurDisk Returns current disk drive DosSelectDisk Selects current disk drive Volume Label Operations DosQFSInfo Returns volume label, date and time of volume creation DosSetFSInfo Adds or changes volume label Figure 9-2. API functions for manipulation of drives, directories, and volume labels. Searching Disk Directories When you request a DosOpen operation on a file, you are performing an implicit search of a directory. OS/2 examines each entry of the directory to match the provided filename; if the file is found, OS/2 copies certain information from the directory into the system file table and returns a handle to the calling program. (The handle is really an indirect pointer to the SFT.) Thus, to test for the existence of a specific file, you can issue a call to DosOpen with the appropriate OpenMode and OpenFlag parameters and check for an error. (To avoid expending handles needlessly, follow with DosClose if the call to DosOpen succeeds.) Of course, this simplistic approach can give misleading results if the process is out of handles or if another process has opened the file with deny-all sharing mode. Most programs need to perform more elaborate (and more reliable) directory searches than the all-or-nothing capability provided by DosOpen. For example, a word processor might need to find and display all files with a DOC (document) extension so that the user can choose one from a menu. To satisfy such needs, OS/2 provides three functions that let you examine disk directories in an efficient hardware-independent fashion: DosFindFirst, DosFindNext, and DosFindClose. DosFindFirst accepts a file specification (which can include a drive, path, and wildcard characters) an attribute, the address and length of a buffer, and a maximum match count. It searches for the first matching file or files in the current or specified directory. If it finds no files with matching names and attributes, DosFindFirst returns an error. If it finds at least one match, it returns a search handle and a match count (1 through the specified maximum) and fills the designated buffer with one or more entries in the format shown in Figure 9-3. In the FAT-based (MS-DOScompatible) file system used in OS/2 versions 1.0 and 1.1, some of the information listed in Figure 9-3 is not available in the disk directory. The dates and times of creation and last access are always returned as zero, and the file allocation is returned as the file size rounded up to agree with the size of the disk's allocation units. Note that when multiple matches are returned, the buffer entries do not have a constant length; the length of each record depends on the length of the filename. The number of matches returned is the minimum of the number available, the number requested, and the number that will fit into the buffer. A successful DosFindFirst must precede a call to DosFindNext. If the file specification that was previously passed to DosFindFirst included wildcard characters and if at least one matching file exists, DosFindNext can be called as many times as necessary with the search handle returned by DosFindFirst to locate all additional matching files. Like DosFindFirst, DosFindNext returns the file information in the calling program's buffer and uses the same format (Figure 9-3). When all matching files have been located, DosFindNext returns an error flag. Byte(s) Contents 00H01H File date of creation 02H03H File time of creation 04H05H File date of last access 06H07H File time of last access 08H09H File date of last write 0AH0BH File time of last write 0CH0FH File size 10H13H File allocation 14H15H File attribute Bit(s) Significance (if set) 0 Read only 1 Hidden 2 System 3 Reserved 4 Directory 5 Archive 6-15 Reserved 16H Length of filename 17H+ Filename followed by null byte Figure 9-3. Format of file information returned in designated buffer by DosFindFirst and DosFindNext. Both DosFindFirst and DosFindNext regard the attribute specified in the DosFindFirst call as inclusive rather than exclusive. If the read-only, hidden, system, archive, or directory attribute bits are set in the request, all normal files are returned in addition to the files or directories with the specified attribute. (The volume label attribute bit receives special treatment, as described later in this chapter.) After DosFindFirst or DosFindNext returns an error indicating that no more files match the specifications, the process should call DosFindClose to release the search handle. Although a large number of search handles (1000) are available for a single process, each active search handle has a system memory overhead and should therefore not be left active any longer than necessary. Figure 9-4 provides an example of searching a directory for all files with the ASM extension and displaying their names. stdout equ 1 ; standard output handle cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed _srec struc ; search result structure... cdate dw ? ; date of creation ctime dw ? ; time of creation adate dw ? ; date of last access atime dw ? ; time of last access wdate dw ? ; date of last write wtime dw ? ; time of last write fsize dd ? ; file size falloc dd ? ; file allocation fattr dw ? ; file attribute fcount db ? ; filename count byte fname db 13 dup (?) ; ASCIIZ filename _srec ends wlen dw ? ; receives bytes written sname db '*.ASM',0 ; target name for search sbuf _srec <> ; instantiate structure to ; receive search results sbuf_len equ $-sbuf ; length of result buffer dirhan dw ? ; receives search handle schcnt dw ? ; receives match count nl db cr,lf ; newline (CR-LF pair) nl_len equ $-nl . . . mov dirhan,-1 ; request search handle mov schcnt,1 ; set max match count ; look for first match... push ds ; target name for search push offset DGROUP:sname push ds ; receives search handle push offset DGROUP:dirhan push 0 ; normal attribute push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset DGROUP:schcnt push 0 ; reserved DWORD 0 push 0 call DosFindFirst ; transfer to OS/2 or ax,ax ; any files found? jnz done ; jump if no match display: ; output one filename... push stdout ; standard output handle push ds ; filename address push offset DGROUP:sbuf.fname mov al,sbuf.fcount xor ah,ah push ax ; filename length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; send new line ... push stdout ; standard output handle push ds ; address of data push offset DGROUP:nl push nl_len ; length of data push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; search for next file... push dirhan ; search handle push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset schcnt call DosFindNext ; transfer to OS/2 or ax,ax ; any more files? jz display ; jump if match found done: push dirhan ; release search handle... call DosFindClose ; transfer to OS/2 . . . Figure 9-4. Using DosFindFirst and DosFindNext to find and display all files with the extension ASM. This simple example does not take advantage of OS/2's ability to return multiple matches on one function call. Note that OS/2 maintains the search context for each search handle in a table outside the process's memory space. Multiple interleaved searches, using different file specifications, can be in progress simultaneously, and OS/2 takes care of all the housekeeping details. The search handles are distinct from file and device handles and do not consume entries in the SFT. Before we move on from the topic of directory searches, we need to mention one more special-purpose OS/2 function. DosSearchPath accepts as parameters a filename and a list of directories (or the name of an environment variable whose value is a list of directories), and searches each directory in order. It returns the fully qualified pathname of the first match found. Although this function allows wildcard characters in the filename, it does not provide any way to continue the search and identify a set of files. It is therefore best suited to locating a particular filesuch as a dynamic link library or a configuration file that is vital to the execution of an application. Directory and Disk Control OS/2 provides three functions for selecting the current directory or for modifying the hierarchical directory structure: DosChDir (select the current directory), DosMkDir (create a directory), and DosRmDir (delete a directory). The directory functions require the address of an ASCIIZ pathname, which can include a drive code and which can be specified completely from the drive's root directory or relative to its current directory. All three directory functions return the usual status in register AX; all can fail for a variety of reasons. The most common cause for returning an error is that some element of the indicated path does not exist. DosRmDir requires special attention because it can return an error for reasons that are difficult to foresee or prevent in the requesting program. For example, the function fails if the directory being deleted contains any files or directories, or if it is the current directory for the command processor of some other screen group. Note that DosChDir selects the current directory for the requesting process only; it does not affect the current directory for other active processes (as you will quickly notice when your program terminates and the CMD.EXE prompt is redisplayed). The current directory is, however, inherited by child processes. An additional function, DosQCurDir, obtains the ASCIIZ pathname of the current directory for the specified or default disk drive. It is commonly used to build up fully qualified path and file specifications that can be displayed, saved across invocations of a program, or passed to other processes. DosQCurDir is called with a drive identifier (0 = default, 1 = A, and so forth), the address of a buffer to receive the pathname, and the address of a variable that contains the length of the buffer. The pathname returned by DosQCurDir does not contain a drive identifier or a leading backslash (\) character; if the current directory is the root directory, then a single null byte is returned. Also note that the function does not return the actual length of the pathname (as you might expect from the behavior of some other calls); your program must scan the string to determine its length. You can rename directories other than the root directory with the function DosMove, although this use is uncommon. The parameters for DosMove are the old and new ASCIIZ pathnames for the directories. The function fails if any element of the path for the old or new directory does not exist, or if the new pathname is the same as an existing file or directory. Directories cannot (unlike files) be moved from one location to another with DosMove. The functions DosQCurDisk and DosSelectDisk obtain or change the current drive. DosQCurDisk also returns a bitmap that indicates which logical drives exist in the system. DosSelectDisk affects only the requesting process; it does not affect the current disk for the current process's parent or any other processes active in the system. Like the current directory, the current drive is inherited by child processes. Volume Labels In FAT (MS-DOScompatible) file systems, a volume label is an optional name, from 1 through 11 characters long, that the user assigns to a disk during a FORMAT operation or later with the LABEL command. You can display the volume label with the DIR, TREE, CHKDSK, LABEL, or VOL command, or you can change it with the LABEL command. The FORMAT program also assigns a semirandom 32-bit binary ID to each disk it formats, but this value is normally invisible to the user and cannot be altered. The distinction between volumes and drives in OS/2 might seem hazy, but it is important. A volume label is associated with a specific storage medium. A drive identifier (such as A:) is associated with a physical device on which a storage medium can be mounted. In the case of fixed disk drives, the medium associated with a drive identifier does not change (hence the name). In the case of floppy disks or other removable media, however, the disk accessed with a given drive identifier might have any volume label or none at all. As a consequence of this distinction, volume labels do not take the place of the single character drive identifier and cannot be used as part of a pathname to identify a file. In current versions of OS/2, their only real use is to identify (inconclusively) removable disks for users and application programs and to help the floppy disk driver detect that a disk has been changed with a file open. In FAT file systems, a volume label is stored as a special type of entry in the root directory with an attribute of 08H. Volume labels cannot, however, be created with DosOpen or located with DosFindFirstan attribute of 08H is rejected by these functions. You must use the functions DosQFSInfo and DosSetFSInfo to add, change, or inspect a volume label (Figure 9-5 on the following page). stdout equ 1 ; standard output handle _vlinf struc ; volume label structure vdate dw ? ; date of creation vtime dw ? ; time of creation vcount db ? ; length of volume name vname db 13 dup (?) ; ASCIIZ volume name _vlinf ends wlen dw ? ; receives bytes written vlinf _vlinf <> ; instantiate structure to ; receive volume info vlinf_len equ $-vlinf ; length of buffer . . . ; get volume label... push 0 ; drive = default push 2 ; file info level 2 push ds ; buffer receives label push offset DGROUP:vlinf push vlinf_len ; size of buffer call DosQFSInfo ; transfer to OS/2 or ax,ax ; call successful? jnz done ; jump if no volume label ; display volume label... push stdout ; standard output handle push ds ; volume label address push offset DGROUP:vlinf.vname mov al,vlinf.vcount ; volume label length xor ah,ah push ax push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 done: . . . Figure 9-5. Using DosQFSInfo to obtain and display a volume label. Sample Program: WHEREIS The listings WHEREIS.C (Figure 9-6) and WHEREIS.ASM (Figure 9-7 on p. 179) contain the source code for an OS/2 "file finder" utility. WHEREIS uses DosFindFirst, DosFindNext, and DosFindClose to perform a recursive search of the directory structure for a pathname (which may include wildcard characters). It displays the names of any matching files on the standard output, which can be directed to a file or to another character device. /* WHEREIS.C A file finder utility that searches the current drive, starting with the root directory (\), for the specified pathname. Wildcard characters can be included. Compile with: C> cl whereis.c Usage is: C> whereis pathname Copyright (C) 1988 Ray Duncan */ #include #include #define API unsigned extern far pascal API DosChDir(char far *, unsigned long); API DosFindClose(unsigned); API DosFindFirst(char far *, unsigned far *, unsigned, void far *, int, int far *, unsigned long); API DosFindNext(unsigned, void far *, int, int far *); API DosQCurDisk(int far *, unsigned long far *); API DosQCurDir(int, void far *, int far *); API DosSelectDisk(int); API DosWrite(unsigned, void far *, unsigned, unsigned far *); #define NORM 0x00 /* file attribute bits */ #define RD_ONLY 0x01 #define HIDDEN 0x02 #define SYSTEM 0x04 #define DIR 0x10 #define ARCHIVE 0x20 struct _srec { /* used by DosFindFirst */ unsigned cdate; /* and DosFindNext */ unsigned ctime; unsigned adate; unsigned atime; unsigned wdate; unsigned wtime; long fsize; long falloc; unsigned fattr; char fcount; char fname[13]; } sbuf; /* receives search results */ int drvno; /* current drive */ int count = 0; /* total files matched */ char sname[80]; /* target pathname */ main(int argc, char *argv[]) { unsigned long drvmap; /* logical drive map */ if(argc < 2) /* exit if no parameters */ { printf("\nwhereis: missing filename\n"); exit(1); } DosQCurDisk(&drvno, &drvmap); /* get current drive */ /* any drive specified? */ if(((strlen(argv[1])) >= 2) && ((argv[1])[1] == ':')) { /* get binary drive code */ drvno = ((argv[1]) [0] | 0x20) - ('a'-1); if(DosSelectDisk(drvno)) /* select drive or exit */ { printf("\nwhereis: bad drive\n"); exit(1); } argv[1] += 2; /* advance past drive */ } strcpy(sname,argv[1]); /* save search target */ schdir("\\"); /* start search with root */ /* advise if no matches */ if(count == 0) printf("\nwhereis: no files\n"); } /* SCHDIR: search directory for matching files and any other directories */ schdir(char *dirname) { unsigned shan = -1; /* search handle */ int scnt = 1; /* max search matches */ DosChDir(dirname, 0L); /* select new directory */ schfile(); /* find and list files */ /* search for directories */ if(!DosFindFirst("*.*", &shan, NORM|DIR, &sbuf, sizeof(sbuf), &scnt, 0L)) { do /* if found directory other */ { /* than . and .. aliases */ if((sbuf.fattr & DIR) && (sbuf.fname[0] != '.')) { schdir(sbuf.fname); /* search the directory */ DosChDir("..", 0L); /* restore old directory */ } /* look for more directories */ } while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0); } DosFindClose(shan); /* close search handle */ } /* SCHFILE: search current directory for files matching string in 'sname' */ schfile() { unsigned shan = -1; /* search handle */ int scnt = 1; /* max search matches */ if(!DosFindFirst(sname, &shan, NORM, &sbuf, sizeof(sbuf), &scnt, 0L)) { do pfile(); /* list matching files */ while(DosFindNext(shan, &sbuf, sizeof(sbuf), &scnt) == 0); } DosFindClose(shan); /* close search handle */ } /* PFILE: display current drive and directory, followed by filename from 'sbuf.fname' */ pfile() { count++; /* count matched files */ pdir(); /* list drive and path */ printf("%s\n", strlwr(sbuf.fname)); /* list filename */ } /* PDIR: display current drive and directory */ pdir() { char dbuf[80]; /* receives current dir */ int dlen = sizeof(dbuf); /* length of buffer */ DosQCurDir(0, dbuf, &dlen); /* get current directory */ if(strlen(dbuf) != 0) /* add backslash to */ strcat(dbuf,"\\"); /* directory if not root */ /* display drive and path */ printf("%c:\\%s", drvno+'a'-1, strlwr(dbuf)); } Figure 9-6. WHEREIS.C, the C language source code for the WHEREIS.EXE utility. title WHEREIS -- File Finder Utility page 55,132 .286 ; ; WHEREIS.ASM ; ; A file finder utility that searches the current drive, starting ; with the root directory (\), for the specified pathname. ; Wildcard characters can be included. ; ; This program requires the modules ARGV.ASM and ARGC.ASM. ; ; Assemble with: C> masm whereis.asm; ; Link with: C> link whereis+argv+argc,,,os2,whereis ; ; Usage is: C> whereis pathname ; ; Copyright (C) 1988 Ray Duncan ; stdin equ 0 ; standard input handle stdout equ 1 ; standard output handle stderr equ 2 ; standard error handle cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed extrn DosChDir:far extrn DosExit:far extrn DosFindClose:far extrn DosFindFirst:far extrn DosFindNext:far extrn DosQCurDir:far extrn DosQCurDisk:far extrn DosSelectDisk:far extrn DosWrite:far _srec struc ; search result structure... cdate dw ? ; date of creation ctime dw ? ; time of creation adate dw ? ; date of last access atime dw ? ; time of last access wdate dw ? ; date of last write wtime dw ? ; time of last write fsize dd ? ; file size falloc dd ? ; file allocation fattr dw ? ; file attribute fcount db ? ; filename count byte fname db 13 dup (?) ; ASCIIZ filename _srec ends DGROUP group _DATA _DATA segment word public 'DATA' root db '\',0 ; name of root directory parent db '..',0 ; alias for parent directory wild db '*.*',0 ; matches all files sname db 64 dup (0) ; filename for search drvno dw 0 ; current drive drvmap dd 0 ; logical drive bitmap dname db 'X:\' ; current drive ID dbuf db 80 dup (?) ; current directory dbuf_len dw ? ; length of buffer sbuf _srec <> ; receives search results sbuf_len equ $-sbuf count dw 0 ; total files matched wlen dw ? ; receives bytes written shandle dw -1 ; directory search handle scount dw 1 ; number of files to return msg1 db cr,lf db 'whereis: no files found' db cr,lf msg1_len equ $-msg1 msg2 db cr,lf msg2_len equ $-msg2 msg3 db cr,lf db 'whereis: missing filename' db cr,lf msg3_len equ $-msg3 msg4 db cr,lf db 'whereis: bad drive' db cr,lf msg4_len equ $-msg4 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP extrn argv:near extrn argc:near whereis proc far call argc ; filename present in cmp ax,2 ; command tail? jae where1 ; jump, filename present ; no filename, exit... mov dx,offset DGROUP:msg3 ; error message address mov cx,msg3_len ; message length jmp where6 ; go terminate where1: ; get current drive... push ds ; receives drive code push offset DGROUP:drvno push ds ; receives drive bitmap push offset DGROUP:drvmap call DosQCurDisk ; transfer to OS/2 mov ax,1 ; get address and length call argv ; of filename parameter ; returns ES:BX = address ; AX = length mov cx,ax ; save length in CX cmp ax,2 ; parameter length > 2? jle where3 ; no, jump cmp byte ptr es:[bx+1],':' ; drive delimiter present? jne where3 ; no, jump mov al,es:[bx] ; get ASCII drive code or al,20h ; fold to lowercase xor ah,ah sub ax,'a'-1 ; convert drive code to mov drvno,ax ; binary and save it cmp ax,1 ; make sure drive valid jb where2 ; jump, bad drive cmp ax,26 ja where2 ; jump, bad drive add bx,2 ; advance command tail sub cx,2 ; pointer past drive code ; set drive for search... push ax ; drive code call DosSelectDisk ; transfer to OS/2 or ax,ax ; drive OK? jz where3 ; jump, drive was valid ; bad drive, exit... where2: mov dx,offset DGROUP:msg4 ; error message address mov cx,msg4_len ; message length jmp where6 where3: mov di,offset DGROUP:sname ; DS:DI = local buffer where4: mov al,es:[bx] ; copy filename to local mov [di],al ; buffer byte by byte... inc di inc bx loop where4 mov byte ptr [di],0 ; append null byte push ds ; make DGROUP addressable pop es ; with ES assume es:DGROUP mov dx,offset DGROUP:root ; start searching with call schdir ; the root directory cmp count,0 ; any matching files found? jne where5 ; yes, exit silently ; no, display 'no files'... push stdout ; standard output handle push ds ; message address push offset DGROUP:msg1 push msg1_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 where5: ; final exit to OS/2... push 1 ; terminate all threads push 0 ; return code=0 (success) call DosExit ; transfer to OS/2 where6: ; common error exit... ; DS:DX = msg, CX = length push stderr ; standard output handle push ds ; address of message push dx push cx ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 1 ; exit code = 1 (error) call DosExit ; transfer to OS/2 whereis endp ; SCHDIR: search a directory for matching ; files and any other directories ; ; Call with: DS:DX = ASCIIZ directory name ; Returns: nothing ; Uses: all registers schdir proc near push shandle ; save old search handle mov shandle,-1 ; initialize search handle ; set search directory... push ds ; directory name address push dx push 0 ; reserved DWORD 0 push 0 call DosChDir ; transfer to OS/2 call schfile ; search current directory ; for matching files ; search for directories... mov scount,1 ; max matches to return push ds ; target name address push offset DGROUP:wild push ds ; receives search handle push offset DGROUP:shandle push 10h ; normal and dir attribute push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset DGROUP:scount push 0 ; reserved DWORD 0 push 0 call DosFindFirst ; transfer to OS/2 or ax,ax ; find anything? jnz schdir3 ; no, jump schdir1: ; found some match... test sbuf.fattr,10h ; is it a directory? jz schdir2 ; no, skip it cmp sbuf.fname,'.' ; is it . or .. entry? je schdir2 ; yes, skip it ; no, new directory found mov dx,offset DGROUP:sbuf.fname call schdir ; call self to search it ; restore old directory... push ds ; address of '..' alias push offset DGROUP:parent push 0 ; reserved DWORD 0 push 0 call DosChDir ; transfer to OS/2 schdir2: ; found at least one match, ; look for next match... mov scount,1 ; max matches to return push shandle ; handle from DosFindFirst push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset DGROUP:scount call DosFindNext ; transfer to OS/2 or ax,ax ; any matches found? jz schdir1 ; yes, go process it schdir3: ; end of search... push shandle ; close search handle call DosFindClose ; transfer to OS/2 pop shandle ; restore previous handle ret ; back to caller schdir endp ; SCHFILE: search current directory for ; files matching string in 'sname' ; ; Call with: nothing ; Returns: nothing ; Uses: all registers schfile proc near push shandle ; save previous handle mov shandle,-1 ; initialize search handle mov scount,1 ; max matches to return push ds ; name to match push offset DGROUP:sname push ds ; receives search handle push offset DGROUP:shandle push 0h ; attribute = normal files push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset DGROUP:scount push 0 ; reserved DWORD 0 push 0 call DosFindFirst ; transfer to OS/2 or ax,ax ; any matches found? jnz schfile3 ; no, terminate search schfile1: ; found matching file... call pfile ; display its name ; look for next match... push shandle ; handle from DosFindFirst push ds ; result buffer address push offset DGROUP:sbuf push sbuf_len ; result buffer length push ds ; receives match count push offset DGROUP:scount call DosFindNext ; transfer to OS/2 or ax,ax ; any more matches? jz schfile1 ; yes, go display filename schfile3: ; end of search... push shandle ; close search handle call DosFindClose ; transfer to OS/2 pop shandle ; restore previous handle ret ; return to caller schfile endp ; PFILE: display current drive and directory, ; followed by filename from 'sbuf.fname' ; ; Call with: nothing ; Returns: nothing ; Uses: all registers pfile proc near inc count ; count matched files call pdir ; display drive:path ; fold name to lower case mov bx,offset DGROUP:sbuf.fname call makelc ; display filename... push stdout ; standard output handle push ds ; filename address push offset DGROUP:sbuf.fname mov al,sbuf.fcount ; filename length xor ah,ah push ax push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; send newline sequence... push stdout ; standard output handle push ds ; address of newline push offset DGROUP:msg2 push msg2_len ; length of newline push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ret ; return to caller pfile endp ; PDIR: display current drive and directory ; ; Call with: nothing ; Returns: nothing ; Uses: AX, BX, CX, DI, ES pdir proc near mov ax,drvno ; convert binary drive add al,'A'-1 ; code to ASCII drive mov dname,al ; and store it for output mov dbuf_len,dbuf_len-dbuf ; initialize length of ; directory buffer ; get current directory... push 0 ; drive 0 = default push ds ; receives ASCIIZ path push offset DGROUP:dbuf push ds ; contains buffer length push offset DGROUP:dbuf_len call DosQCurDir ; transfer to OS/2 mov di,offset DGROUP:dbuf ; address of path cmp byte ptr [di],0 ; is path = root? je pdir1 ; yes, jump mov cx,dbuf_len-dbuf ; no, scan for null xor al,al ; byte at end of path... repne scasb mov byte ptr [di-1],'\' ; append a backslash pdir1: mov bx,offset DGROUP:dname ; fold everything to call makelc ; lowercase ; now display drive:path... push stdout ; standard output handle push ds ; address of pathname push offset DGROUP:dname sub di,offset DGROUP:dname ; length of drive and path push di push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ret ; back to caller pdir endp ; MAKELC: convert ASCIIZ string to lowercase ; ; Call with: DS:BX = string address ; Returns: nothing ; Uses: BX makelc proc near make1: cmp byte ptr [bx],0 ; end of string? je make3 ; jump if end cmp byte ptr [bx],'A' ; check next character jb make2 ; jump, not uppercase cmp byte ptr [bx],'Z' ja make2 ; jump, not uppercase or byte ptr [bx],20h ; fold to lowercase make2: inc bx ; advance through string jmp make1 make3: ret ; back to caller makelc endp _TEXT ends end whereis Figure 9-7. WHEREIS.ASM, the MASM source code for the WHEREIS.EXE utility. The source code for the subroutines ARGV.ASM and ARGC.ASM can be found in Chapter 8. WHEREIS has four major subroutines. A search begins when the program's main routine calls the first subroutine, schdir, with a plain ASCIIZ backslash (\) signifying the root directory for the current or specified drive. The schdir subroutine makes that directory current using DosChDir and calls schfile to find and display any matching files. The subroutine pfile, which is called by schfile, works together with pdir to display a fully qualified pathname. It obtains the current drive and directory with DosQCurDisk and DosQCurDrive, and then formats and displays them along with the filename that was discovered by schfile. The schdir subroutine then searches the current directory for other directories and calls itself recursively with the name of each directory found. To compile and link the source file WHEREIS.C into the executable file WHEREIS.EXE, enter the command: [C:\] CL WHEREIS.C To assemble and link the files WHEREIS.ASM and WHEREIS.DEF (Figure 9-8) into the executable file WHEREIS.EXE, you also need the modules ARGC.ASM and ARGV.ASM from Chapter 8. Gather the four files together in the same directory and enter the commands: [C:\] MASM WHEREIS.ASM; [C:\] MASM ARGC.ASM; [C:\] MASM ARGV.ASM; [C:\] LINK WHEREIS+ARGC+ARGV,,,OS2,WHEREIS To run either version of the utility, enter a command of the following form: [C:\] WHEREIS pathname.ext If no matching files are found, or if the command line argument is missing, an appropriate error message is displayed. The recursive use of DosFindFirst, DosFindNext, and DosFindClose in the WHEREIS program is not compatible with the Family API restrictions. Thus, WHEREIS cannot be bound for use in real mode. WHEREIS could be made considerably more efficient by eliminating the calls to DosChDir and by requesting more than one match per call to DosFindFirst and DosFindNext; these enhancements have been left, so the saying goes, as exercises for the reader. NAME WHEREIS WINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 9-8. WHEREIS.DEF, the module definition file for the WHEREIS.EXE utility. Chapter 10 Disk Internals Block storage devices for OS/2 versions 1.0 and 1.1 are organized as FAT (file allocation table) file systems, compatible with MS-DOS versions 2.0 and later. This is convenient for software developers because they can more easily transport data between MS-DOS and OS/2 systems and develop programs for MS-DOS under OS/2 (and vice versa). FAT file systems are organized according to a rigid scheme that is easily understood and manipulated. Although most programmers never need to access the special control areas of such a device directly, an understanding of their structure leads to a better understanding of constraints on file size and file system behavior. Distinct Volume Areas OS/2 views each logical volumewhether it corresponds to an entire physical medium (such as a diskette) or only a part of a larger fixed disk as a continuous sequence of logical sectors, starting with sector 0. (You can also implement a "logical disk volume" on other types of storage; for example, RAM disks map a disk structure onto a block of memory.) The available sectors are divided among several areas of fixed size as shown in Figure 10-1 on the following page: The boot sector An optional reserved area One or more FATs The root directory The files area OS/2 translates file management requests from application programs into requests to the disk driver for transfers of logical sectors; in doing so, it uses the information in the volume's boot sector, FAT, and root directory. The sizes of these areas can vary from disk to disk, but all the information needed to interpret the structure of a particular disk is on the disk itself. The disk driver maps logical sectors onto actual physical addresses (head, cylinder, and sector). It is the operating system module that controls the disk by reading and writing the I/O ports assigned to its adapter. Any interleaving (the mapping of consecutive physical sector addresses onto noncontiguous physical sectors to improve performance) is done at the adapter level. OS/2 disk drivers are typically written in assembly language. They must be capable of functioning in both real and protected mode and are carefully tuned for optimum performance. OS/2 always installs the "base" driver for IBM-compatible disk devices (DISK01.SYS or DISK02.SYS) during system initialization. To load disk drivers supplied by manufacturers of non-IBM-compatible disk drives, include a DEVICE= statement in the CONFIG.SYS file on the boot disk. Ŀ Boot sector Reserved area Ĵ File allocation table #1 Ĵ Possible additional copies of the FAT Ĵ Root directory Ĵ Files area Figure 10-1. Map of a typical OS/2 logical volume. The boot sector (logical sector 0) contains the OEM identification, BIOS parameter block (BPB), and disk bootstrap. An optional reserved area of variable size is followed by one or more copies of the file allocation table, the root directory, and the files area. Boot Sector Logical sector zero is known as the boot sector and contains all the critical information about a volume's characteristics (Figures 10-2, 10-3, and 10-4 on the following pages). The first byte in the sector is always an 80x86 jump instructioneither a normal intrasegment JMP (opcode 0E9H) followed by a 16-bit displacement or a "short jump" (opcode 0EBH) followed by an 8-bit displacement and an NOP instruction (opcode 90H). If one of these two JMP opcodes is not present, the disk has not been formatted or was not formatted for use with OS/2 or MS-DOS. Of course, the presence of the JMP opcode is not in itself an assurance that the disk contains an OS/2-compatible file system. 00H Ŀ E9 XX XX or EB XX 90 03H Ĵ OEM name and version 0BH ĴĿ Bytes per sector 0DH Ĵ Sectors per cluster 0EH Ĵ Reserved sectors, starting at 0 10H Ĵ Number of FATs 11H Ĵ Number of root directory entries 13H Ĵ BIOS Total sectors in logical volume parameter 15H Ĵ block Medium descriptor byte 16H Ĵ Sectors per FAT 18H Ĵ Sectors per track 1AH Ĵ Number of heads 1CH Ĵ Number of hidden sectors 20H Ĵ Total sectors in logical volume (if volume > 32 MB) 24H Ĵ Physical drive number 25H Ĵ Reserved 26H Ĵ Extended boot record signature 27H Ĵ 32-bit binary volume ID 2BH Ĵ Bootstrap Figure 10-2. Map of the boot sector of an OS/2 disk. Note JMP at offset zero, OEM identification field, BIOS parameter block, 32-bit binary volume ID, and disk bootstrap code. 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 EB 29 90 49 42 4D 20 31 30 2E 31 00 02 04 01 00 k).IBM 10.1..... 0010 02 00 02 EE FF F8 40 00 11 00 05 00 11 00 00 00 ...n.x@......... 0020 00 00 00 00 80 00 28 15 E4 A5 23 33 C0 8E D0 BC ......(.d%#3@.P< 0030 00 7C BB C0 07 8E DB A0 10 00 F7 26 16 00 03 06 .|;@..[ ..w&.... . . . 01C0 72 65 73 74 61 72 74 0D 0A 74 68 65 20 73 79 73 restart..the sys 01D0 74 65 6D 2E 0D 0A 00 4F 53 32 4C 44 52 20 20 20 tem....OS2LDR 01E0 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .............. 01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA ..............U* Figure 10-3. Partial dump of an OS/2 version 1.1 fixed disk boot sector. See Figures 10-2 and 10-4. jmp $+39 ; jump to bootstrap nop db 'IBM 10.1' ; OEM identification ; BIOS parameter block... dw 512 ; bytes per sector db 4 ; sectors per cluster dw 1 ; reserved sectors db 2 ; number of FATs dw 512 ; root directory entries dw 65518 ; total sectors db 0f8h ; medium descriptor byte dw 64 ; sectors per FAT dw 17 ; sectors per track dw 5 ; number of heads dd 17 ; hidden sectors dd 0 ; large number of sectors . . . Figure 10-4. Partial disassembly of the boot sector shown in Figure 10-3. Following the initial JMP instruction, eight bytes are reserved for the OEM identification field. This field contains the name of the OS/2 OEM (the computer manufacturer who adapted the OS/2 system for a particular hardware configuration) and an OEM internal version number as an ASCII string. The third component of the boot sector is the BIOS parameter block (BPB). This structure describes the physical characteristics of the disk and allows the device driver to calculate the proper physical disk address for a given logical sector number; it also contains information that OS/2 and various system utilities use to calculate the size and location of the FAT, the root directory, and the files area. The BPB is followed by a 32-bit binary volume ID, generated by the FORMAT utility, which is different for each volume. The remainder of the boot sector contains the disk bootstrap program, which is the destination of the JMP instruction at the beginning of the sector. The ROM bootstrap reads the disk bootstrap into memory when the system is turned on or restarted; the disk bootstrap in turn reads the file OS2LDR into memory and transfers control to it. The system initialization process is described in detail in Chapter 2. Reserved Area The boot sector is actually part of a reserved area that comprises one or more sectors. The size of the reserved area is described by the reserved sectors field of the BIOS parameter block, at offset 0EH in the boot sector. Remember that the number in the BPB field includes the boot sector itself; so if it contains the value 1 (as it does on most floppy disk types), the length of the reserved area, as shown in Figure 10-1 on p. 196, is actually 0 sectors. File Allocation Table When a file is created or extended, disk sectors are assigned to it from the files area in powers of 2 known as allocation units or clusters. The number of sectors per cluster for a given medium is defined in the BIOS parameter block and can be found at offset 0DH in the disk's boot sector. For example, the following table shows the cluster sizes for some common media: Disk Type Power of 2 Sec/Cluster 180 KB floppy 0 1 360 KB floppy 1 2 1.2 MB floppy 2 4 Fixed disk 2, 3... 4, 8... The FAT is divided into fields that correspond directly to the assignable clusters on the disk. These fields are either 12 bits or 16 bits, depending on the size of the medium (12 bits if the disk contains fewer than 4087 clusters, 16 bits otherwise). The first two entries in a FAT are always reserved. On IBM-compatible media, the first eight bits of the first FAT entry contain a copy of the medium descriptor byte (Figure 10-5), which is also found in the boot sector's BPB. The second, third, and (if applicable) fourth bytes, which constitute the remainder of the first two reserved FAT fields, always contain 0FFH. The currently defined IBM-format medium descriptor bytes are as follows: Descriptor Byte Medium Characteristics Value Size Sides Sectors 0F0H 3.5" 2 18 0F8H Fixed disk 0F9H 5.25" 2 15 or 3.5" 2 9 0FCH 5.25" 1 9 0FDH 5.25" 2 9 0FEH 5.25" 1 8 0FFH 5.25" 2 8 Aside from the first two reserved entries of the FAT, the remainder of the entries describe the usage of their corresponding disk clusters. The contents of the FAT fields are interpreted as follows: Value Meaning (0)000H The cluster is available (F)FF0H(F)FF6H Reserved cluster (F)FF7H Bad cluster if not part of chain (F)FF8H(F)FFFH Last cluster of a file (X)XXXH Next cluster in the file 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 F9 FF FF 03 F0 FF FF 6F 00 07 80 00 09 A0 00 0B 0010 C0 00 FF EF 00 0F 00 01 11 20 01 13 40 01 15 60 0020 01 17 80 01 19 A0 01 1B F0 FF 1D E0 01 1F 00 02 . . . Figure 10-5. Dump of the first block of the file allocation table for an OS/2 version 1.1 floppy disk. Notice that the first byte of the FAT contains the medium descriptor byte for a 5.25-inch, double-sided, 15-sector (1.2 MB) floppy disk. Each file's entry in a disk directory contains the number of the first cluster assigned to that file, which is used as an entry point into the FAT. From the entry point on, each FAT slot contains the cluster number of the next cluster in the file, until a last cluster mark is encountered. At the computer manufacturer's option, a volume can contain two or more identical copies of the FAT. OS/2 simultaneously updates all copies whenever space in the files area is allocated or released. If a FAT sector becomes unreadable, the other copies are tried until a successful read is obtained or until all copies are exhausted. As part of its procedure for checking the integrity of a disk, the CHKDSK program compares the multiple copies (usually two) of the FAT to verify that they are both readable and consistent. Root Directory A volume's root directory immediately follows the FAT(s). It contains 32-byte entries that describe files, other directories, and the optional volume label (Figures 10-6 and 10-7). An entry beginning with the byte E5H is available for reuse; it represents a file or directory that has been erased. An entry beginning with a null (zero) byte indicates the logical end of directory; that entry and all subsequent entries are unused. The root directory has a number of special properties. Its size and position are fixed; they are determined by the FORMAT program when a disk is initialized. The number of entries in the root directory and its location on the disk can be obtained from the BPB in the boot sector. Attribute Filename field Extension field byte Reserved 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 4F 53 32 4C 44 52 20 20 20 20 20 22 00 00 00 00 OS2LDR ".... 0010 00 00 00 00 00 00 02 77 4C 11 02 00 00 10 00 00 .......wL....... 0020 4F 53 324B 52 4E 4C20 2020 2022 00 0000 00 OS2KRNL ".... 0030 00 00 0000 00 00 5876 4C11 0400 25 A704 00 ......XvL...%... 0040 42 4F 4F54 44 49 534B 2020 2008 00 0000 00 BOOTDISK (.... 0050 00 00 0000 00 00 A100 2100 000000 0000 00 ........!....... . . Starting . cluster field Reserved Time field Date File size field field Attribute byte for volume label entry Figure 10-6. Partial hex dump of the first sector of the root directory for an OS/2 version 1.1 disk. The directory entries for the two system files (OS2LDR and OS2KRNL) and a volume label (BOOTDISK) are shown. 00H Ŀ Value Special Interpretation 00H Directory entry has never been used; logical end of directory. E5H File or directory has been erased. 2EH Entry is a directory alias. If the next byte is also 2EH, then the cluster field contains the first cluster number of the parent directory (0000H if the parent is root). Filename 08H Ĵ Extension 0BH Ĵ File attribute Bit(s) Significance 0 Read-only 1 Hidden* 2 System* 3 Volume label* (found only in root) 4 Directory* 5 Archive (set when file is modified) 6-7 Reserved * Excluded from normal searches 0CH Ĵ Reserved 16H Ĵ Time created or last updated Bits Contents 00-04H 2-second increments (0-29) 05-0AH Minutes (0-59) 0B-0FH Hours (0-23) 18H Ĵ Date created or last updated Bits Contents 00-04H Day of month (1-31) 05-08H Month (1-12) 09-0FH Year (relative to 1980) 1CH Ĵ File size Interpreted as a 4-byte integer, with the two low-order bytes of the number stored first. 20H Figure 10-7. Format of a single entry in a disk directory. Total length 32 bytes (20H bytes). The values that are explained in the filename field refer only to the first character in that field. Files Area The remainder of the volume after the root directory is known as the files area, or data area. The disk sectors in this area are viewed as a pool of clusters, each containing one or more logical sectors, depending on the disk format. Each cluster has a corresponding entry in the FAT that describes its current usage: available, reserved, assigned to a file, or unusable (due to medium defects). Because the first two fields of the FAT are reserved, the first cluster in the files area is assigned the number 2. When a file is extended, the FAT is searched from the most recently allocated cluster until a free cluster (designated by a zero FAT field) is found, and that FAT field is changed to a last cluster mark. If the file was previously emptyhad no clusters assigned to itthe number of the newly assigned cluster is inserted into the directory entry for the file. Otherwise, the FAT entry of the previous last cluster for the file is updated to point to the new last cluster. Directories other than the root directory are simply a special type of file. Their storage is allocated from the files area, and their contents are 32-byte entriesin the same format as those used in the root directorythat describe files or other directories. Directory entries that describe other directories contain an attribute byte with bit 4 set, zero in the file length field, and the date and time that the directory was created (Figure 10-8 on the following page). The first cluster field, of course, points to the first cluster in the files area that belongs to the directory. (The directory's other clusters can be found only by tracing through the FAT.) All directories except for the root directory contain two special entries with the names . and .. that are put in place when a directory is created. These entries cannot be deleted. The . entry refers to the current directory; its cluster field points to the cluster in which it is found. The .. entry refers to the directory's "parent" (the directory immediately above it in the tree structure), and its cluster field points to the first cluster of the parent. If the parent is the root directory, the cluster field of the .. entry contains zero (Figure 10-9 on the following page). Subdirectory name Attribute byte indicating a Reserved . subdirectory entry . . 0080 4D 59 44 49 52 20 20 20 20 20 20 10 00 00 00 00 MYDIR ..... 0090 00 00 00 00 00 00 87 9A 9B 0A 2A 00 00 00 00 00 ..........*..... . . Starting . Time sub- cluster of directory subdirectory Reserved created file Date sub- File size indicated directory as zero bytes created Figure 10-8. Extract from the root directory of an OS/2 disk, showing the entry for a subdirectory named MYDIR. The entry has bit 4 in the attribute byte set, and the cluster field points to the first cluster of the subdirectory file. The date and time stamps are valid, but the file length is zero. Special entry for Attribute bytes indicating current subdirectory a subdirectory 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 2E 20 20 20 20 20 20 20 20 20 20 10 00 00 00 00 . ..... 0010 00 00 00 00 00 00 87 9A 9B 0A 2A 00 00 00 00 00 ..........*..... 0020 2E 2E 20 20 20 20 20 20 20 20 20 10 00 00 00 00 .. ..... 0030 00 00 00 00 00 0087 9A 9B 0A 00 0000 00 00 00 ................ 0040 4D 59 46 49 4C 4520 20 44 41 54 2000 00 00 00 MYFILE DAT .... 0050 00 00 00 00 00 0098 9A 9B 0A 2B 0015 00 00 00 ..........+..... 0060 00 00 00 00 00 0000 00 00 00 00 0000 00 00 00 ................ 0070 00 00 00 00 00 0000 00 00 00 00 0000 00 00 00 ................ . . Special entry Attribute bytes indicating . for parent directory a subdirectory Figure 10-9. Dump of the first block of the directory MYDIR. Note the . and .. entries. This directory contains exactly one file, named MYFILE.DAT. Fixed Disk Partitions Fixed disks have another layer of organization beyond the logical volume structure we discussed earlier. The FDISK utility organizes a fixed disk into one or more partitions consisting of an integral number of cylinders. Each partition can contain an independent file system and, for that matter, a different operating system. The first physical sector on a fixed disk contains the master boot record, which is laid out as follows: Bytes Contents 000H1BDH Reserved 1BEH1CDH Partition #1 descriptor 1CEH1DDH Partition #2 descriptor 1DEH1EDH Partition #3 descriptor 1EEH1FDH Partition #4 descriptor 1FEH1FFH Signature word (AA55H) The partition descriptors in the master boot record define the size, location, and type of each partition: Byte(s) Contents 00H Active flag (0 = not bootable, 80H = bootable) 01H Starting head 02H03H Starting sector/cylinder 04H Partition type 0 = not used 1 = FAT file system, 12-bit FAT entries 4 = FAT file system, 16-bit FAT entries 5 = extended OS/2 or MS-DOS partition 6 = huge (>32 MB) FAT file system 05H Ending head 06H07H Ending sector/cylinder 08H0BH Starting sector for partition, relative to beginning of disk 0CH0FH Partition length in sectors The active flag, which indicates that the partition is bootable, can be set on only one partition at a time. A hex dump of a typical master boot record is shown in Figure 10-10 on the following page. OS/2 treats partition types 1, 4, and 6 as normal logical volumes and assigns them their own drive identifiers during the system boot process. Partition type 5 can contain multiple logical volumes and has a special extended boot record that describes each volume. All OS/2 or MS-DOS partitions are initialized with the FORMAT utility, which creates the file system within the partition (boot record, file allocation table(s), root directory, and files area) and optionally places a bootable copy of the operating system in the file system. 0000 . Partition 1 Active (bootable) . type byte partition flag . 0180 00 00 0000 00 00 00 00 00 00 00 00 00 0000 00 0190 00 00 0000 00 00 00 00 00 00 00 00 00 0000 00 01A0 00 00 0000 00 00 00 00 00 00 00 00 00 0000 00 01B0 00 00 0000 00 00 00 00 00 00 00 00 00 00 80 011st partition entry 01C0 01 00 04 04 D1 02 11 00 00 00 EE FF 00 00 00 002nd partition entry 01D0 C1 04 05 04 D1 FD 54 00 01 00 02 53 00 00 00 003rd partition entry 01E0 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 004th partition entry 01F0 00 00 0000 00 00 00 00 00 00 00 00 00 00 55 AA Partition 2 type byte Signature word Figure 10-10. A partial hex dump of a master block from a fixed disk. This disk contains two partitions. The first partition entry (starting at offset 01BEH) has a 16-bit FAT and is marked "active" to indicate that it contains a bootable copy of OS/2. The second partition entry (starting at offset 01CEH) is an "extended" partition. The third and fourth partition entries are not used in this example. Direct Disk Access OS/2 provides mechanisms for application programs to bypass the file system and to read and write block devices at the logical sector level. These mechanisms let you write low level disk utilities (such as file recovery and repair programs) in a hardware-independent fashion. The API functions that a program uses for direct disk access are the already familiar ones: DosOpen, DosChgFilePtr, DosRead, DosWrite, DosClose, and DosDevIOCtl. Instead of a filename, DosOpen is provided with an ASCIIZ drive identifier, and the "DASD" flag (bit 15) in the OpenMode parameter is set. If the drive exists, DosOpen returns a handle (as usual), but this handle refers to the entire logical volumerepresented as though it were a single file. After you obtain a handle, use it with DosDevIOCtl Category 8 Function 00H to "lock" the logical volume so that no other processes can use it during the direct disk access operations. The lock operation fails if any files on the volume are currently open. (This means that you cannot lock the boot drive because several dynlink libraries on that drive are always open.) After the lock succeeds, any subsequent requests to open a file on that volume will return an error. Once the volume is locked, you can use DosChgFilePtr, DosRead, and DosWrite to move to any location and read or write data. It is most convenient to seek, read, and write in multiples of 512 bytes because the physical disk sector size on IBM-compatible media is always 512 bytes, but OS/2 does not constrain you to use this sector size. When direct disk access operations are completed, unlock the logical volume with DosDevIOCtl Category 8 Function 01H; then release the logical volume handle in the usual fashion with DosClose. Figure 10-11 contains sample code that opens and locks logical drive E for direct access, reads the drive's boot sector into a buffer, and then unlocks the drive and releases the handle. secsize equ 512 ; physical sector size dname db 'E:',0 ; logical drive identifier dhandle dw ? ; receives drive handle daction dw ? ; receives DosOpen action rlen dw ? ; actual bytes read bootsec db secsize dup (?) ; boot sector read here parblk db 0 ; DosDevIOCtl dummy ; parameter block . . . ; open drive E for direct access... push ds ; address of drive name push offset DGROUP:dname push ds ; receives drive handle push offset DGROUP:dhandle push ds ; receives DosOpen action push offset DGROUP:daction push 0 ; file allocation (N/A) push 0 push 0 ; file attribute (N/A) push 01h ; action: open if exists, ; fail if doesn't push 8012h ; mode: DASD, read/write, ; deny-all push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 or ax,ax ; was open successful? jnz error ; jump if function failed ; lock logical drive... push 0 ; data buffer address push 0 ; (not needed) push ds ; parameter buffer address push offset DGROUP:parblk push 0 ; function push 8 ; category push dhandle ; drive handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; was lock successful? jnz error ; jump if function failed ; now read boot sector... push dhandle ; drive handle push ds ; buffer address push offset DGROUP:bootsec push secsize ; buffer length push ds ; receives actual length push offset DGROUP:rlen call DosRead ; transfer to OS/2 or ax,ax ; was read successful? jnz error ; jump if function failed cmp rlen,secsize ; actual = expected size? jnz error ; jump if not enough data ; unlock logical drive... push 0 ; data buffer address push 0 ; (not needed) push ds ; parameter buffer address push offset DGROUP:parblk push 1 ; function push 8 ; category push dhandle ; drive handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; was unlock successful? jnz error ; jump if function failed ; close logical drive... push dhandle ; drive handle call DosClose ; transfer to OS/2 or ax,ax ; did close succeed? jnz error ; jump if function failed . . . Figure 10-11. Sample code for direct disk access. This sequence of calls opens and locks logical drive E for direct access, reads the drive's boot sector into a buffer, and then unlocks the drive and releases the handle. SECTION 4 ADVANCED OS/2 TECHNIQUES Chapter 11 Memory Management RAM is the fundamental resource in an OS/2 system and also the most precious. Part of the RAM is statically allocated for use by vital OS/2 modules and data structures and by the DOS compatibility environment (if it is enabled). The remainder is administered by the OS/2 memory manager and can be thought of as a large pool (sometimes called the global heap) from which segments can be allocated and released as necessary. The OS/2 kernel dynamically allocates memory for the following: Swappable operating system modules and data structures The code, data, and stacks of transient processes Objects created by processes but controlled by the operating system, such as system semaphores and pipes OS/2 exports the memory manager services to applications through a number of API calls (Figure 11-1 on the following page). Application programs can use these services to dynamically create and destroy memory segments for buffers, arrays, tables, and other work areas. The memory management functions fall into three categories: Allocation and release of global memory segments Local memory pool management Allocation and release of named, shareable memory segments The first two categories are discussed in this chapter; the last is deferred to Chapter 13 because it falls naturally into the realm of interprocess communication. But first, a brief review of the 80286 hardware support for memory protection and virtual memory is in order. (The 80386, when running OS/2 version 1.0 or 1.1, acts exactly like an 80286.) OS/2 Function Description Global Memory Management DosAllocSeg Allocates memory block with maximum size 64 KB DosCreateCSAlias Returns executable selector for previously allocated block DosFreeSeg Releases memory block DosLockSeg Locks discardable segment DosMemAvail Returns size of largest contiguous block of physical memory DosReallocSeg Changes size of previously allocated block DosSizeSeg Returns size of previously allocated memory block DosUnlockSeg Unlocks discardable segment Huge Block Management DosAllocHuge Allocates huge memory block (larger than 64 KB) DosGetHugeShift Returns incrementing value for selectors of huge block DosReallocHuge Changes size of previously allocated huge block Shared Memory Management (see Chapter 13) DosAllocShrSeg Creates named shareable memory block DosGetSeg Makes selector from another process addressable DosGetShrSeg Obtains selector for existing named shared memory block DosGiveSeg Creates selector for use by another process Local Memory Management DosSubAlloc Allocates memory block from local pool DosSubFree Releases block to local pool DosSubSet Initializes or resizes local memory pool Figure 11-1. OS/2 memory management functions at a glance. Memory Protection The Intel 80286 microprocessor starts operating in real mode, which is essentially an 8086 emulation mode. In real mode, segment registers contain paragraph addresses, which you can manipulate directly; you can address a maximum of 1 MB of memory. To form a 20-bit physical memory address, the hardware multiplies the value in a segment register by 16 and combines it with a 16-bit offset (Figure 11-2). Operating systems (such as MS-DOS) that run in real mode cannot intervene when a program does not use memory properly, nor can they prevent one program from writing into another's memory space. Ŀ Segment register *16 Ŀ 16-bit offset Ŀ 20-bit physical address Figure 11-2. Generation of physical addresses in real mode. OS/2 runs the 80286 processor in protected mode, which presents the programmer with hardware architecture that is quite different from that of the 8086. In protected mode, the contents of segment registers are logical selectors that contain several bitfields (Figure 11-3), the largest of which is an index into a descriptor table. Each descriptor represents a memory segment and contains the physical base address, length, and segment attributes (executable, read-only, or read/write). The descriptor also specifies the ring, or privilege level, at which a program must be running to access the segment (Figure 11-4 on the following page). F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ T RPL Requested privilege level 0 = GDT 1 = LDT Descriptor table index Figure 11-3. Structure of a protected mode selector. The actual privilege level is the numeric maximum of the RPL (requested privilege level) of the selector and the CPL (current privilege level) of the process. When an instruction references memory in protected mode, a segment register provides a selector and indicates a specific descriptor. The base address in the descriptor is combined with a 16-bit offset to form a 24-bit physical memory address (Figure 11-5 on p. 213). Consequently, you must treat a protected mode selector much like a file handle: It is simply a token for a piece of memory. You cannot assume that a selector is unique across processes, and your program should not (with one exception) manipulate selectors arithmetically. 0 Ŀ 1 Segment length 2 Ĵ 3 Segment base address 4 5 Ĵ Access rights Bit(s) Significance (if set) 0 Accessed 1 Writable (data) Readable (text) 2 Direction (data) Conforming (text) 3-4 Descriptor type 10 = data 11 = text 5-6 Privilege level 7 Present 6 Ĵ 7 Reserved 8 Figure 11-4. Structure of protected mode data and text (executable) descriptors on the Intel 80286. The descriptors for system segments and call gates have a somewhat different format and are beyond the scope of this book. An OS/2 system contains many descriptor tables: a single global descriptor table (GDT) and, for each process, a local descriptor table (LDT). The GDT contains descriptors for all the LDTs as well as for all segments owned or occupied by the operating system; each process's LDT contains descriptors for the segments that are owned or shared by that process. Only the operating system is entrusted with both the privilege level and the knowledge of physical memory layout that are necessary to create and destroy memory segments through manipulation of descriptor tables. The CPU hardware, using the descriptor tables, enforces memory protection between processes. If a process tries to load a segment register with a selector that is not valid for its LDT or for an entry in the GDT that requires a higher privilege level, a hardware trap occurs. (A trap is like a hardware interrupt but is internally generated.) OS/2 receives control, terminates the offending process, and displays a message to the user. Ŀ Selector in segment register Ŀ Ĵ  Ŀ Ĵ ڿڿڿڿڿڿڿ Descriptor table Ŀ 24-bit physical base address Ŀ 16-bit offset Ŀ 24-bit physical address Figure 11-5. Generation of physical addresses in protected mode. Virtual Memory Virtual memory allows a process to request and own more bytes of RAM than physically exist in the system. Hardware support for memory protection combines with the operation of two OS/2 modulesthe virtual memory manager (VMM) and the swapperto present the illusion to the task that all its segments are simultaneously present and accessible. When a process attempts to allocate additional memory, the VMM tries to satisfy the request by moving segments to coalesce free blocks of physical memory, by throwing away read-only data segments or text segments which were loaded from an executable file on fixed disk Segments loaded from a floppy disk EXE or DLL file are swapped rather than discarded because the user might complicate matters by changing disks before the segment is needed again. , or by tossing out unlocked, discardable data segments. These strategies failing, the VMM invokes the swapper to move one or more segments to a swap file on disk, choosing the segments with an LRU algorithm. The physical memory occupied by each discarded or swapped segment is then freed, and the present bit in the segment's descriptor is cleared to indicate that the memory segment is not resident. When a selector for a discarded or swapped-out segment is used in a memory access, a hardware trap occurs. The VMM gains control and reads the needed segment from the disk into physical memory from the swap file or from the program's original EXE or DLL file, possibly moving segments or writing another segment out to the disk to make room. It then updates the descriptor to point to the new physical address of the reloaded segment, sets the present bit in the descriptor, and restarts the process's instruction that was trying to access the segment. Because the system implements virtual memory, the presence or absence in physical memory of any particular segment is completely transparent to the process that owns it. If a memory address is accessed that is not currently mapped to some piece of physical RAM, its segment is loaded before the access is allowed to complete. Correspondingly, the amount of memory that can be committed by all the processes in the system combined is usually limited only by the amount of physical memory plus the swap space on the disk. (Although other system-wide limits do exist, they would not be encountered during the execution of a normal application.) Global Memory An application program allocates a global memory segment by calling DosAllocSeg with three parameters: a segment size in bytes (065,535), the address of a variable to receive a selector, and a word of flags. A segment size of 0 is treated as a special case and results in a 65,536-byte segment. The flags word controls whether the allocated segment can be discarded in low memory situations or shared with other processes (Figure 11-6). Discardable segments are discussed further below, and shared memory segments are covered in Chapter 13; for the moment, note that a flags word of zero is appropriate for most circumstances. F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ 1 = shareable with DosGiveSeg 1 = shareable with DosGetSeg 1 = discardable Reserved Figure 11-6. Flags word for DosAllocSeg and DosAllocHuge. If segment motion or segment swapping (or both) has not been disabled with the MEMMAN directive in the CONFIG.SYS file, segments can be written to disk or shuffled in memory (or both) to satisfy the allocation request. In such situations, the execution time of a DosAllocSeg call can vary widely because the delay involved in segment swapping or movement depends on many factors (such as the location of the disk read/write head and the size of the segment). A DosAllocSeg call fails if an invalid value is supplied for the flags word, if all physical memory is committed and swapping is disabled, or if all physical memory is committed and swapping is enabled but the disk holding the swap file is full. DosAllocSeg can also fail if the VMM's or swapper's table space is exhausted or if the per-process limit on the number of shared or unshared segments is reached The OS/2 version 1.0 per-process limits are: (1/3) * 8192 unshared segments, (2/3) * 8192 shared segments. The OS/2 version 1.1 limits are: (1/4) * 8192 unshared segments, (3/4) * 8192 shared segments. none of these should be limiting factors for typical applications. After the system allocates a segment, the owning process can resize it with the function DosReallocSeg. The function is called with the selector and the new desired size in bytes (065,535, with 0 interpreted as 65,536). As in the case of DosAllocSeg, if other segments must be moved or swapped to satisfy the request, the time required for the call is unpredictable. A DosReallocSeg call fails if the selector is not valid or if the program that invokes it is attempting to enlarge a segment while the physical memory and disk swap space are exhausted. A process can release a memory segment allocated with DosAllocSeg by passing its selector to DosFreeSeg or by terminating. DosFreeSeg removes the descriptor for the segment from the process's LDT, so any subsequent accesses with the selector result in a GP fault. If the segment is unshared, the physical memory that it occupies is returned to the global pool; if the segment is shared, it is not destroyed until all processes that have access to it have released it or terminated. Figure 11-7 contains an example of allocating, resizing, and releasing a global memory block. sel dw ? ; receives selector . . . ; allocate global segment... push 4000h ; initially get 16 KB push ds ; receives selector push offset DGROUP:sel push 0 ; 0 = not shareable ; or discardable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was allocation successful? jnz error ; jump if function failed . . . ; now resize segment ; to 65,536 bytes... push 0 ; 0 indicates 64 KB push sel ; segment selector call DosReallocSeg ; transfer to OS/2 or ax,ax ; was resize successful? jnz error ; jump if function failed . . . ; fill block with -1s... mov cx,8000h ; words to initialize mov es,sel ; load segment selector xor di,di ; start at offset zero mov ax,-1 ; load initializing value cld ; safety first rep stosw ; fill the block . . . ; now release the block... push sel ; segment selector call DosFreeSeg ; transfer to OS/2 or ax,ax ; was release successful? jnz error ; jump if function failed . . . Figure 11-7. Allocating a 16 KB memory block with DosAllocSeg, resizing it to 64 KB with DosReallocSeg, filling it with -1s, and then releasing it with DosFreeSeg. Huge Memory Blocks As we saw earlier, protected mode memory addresses are generated by combining a selector with a 16-bit offset. This means that the largest continuously addressable, physically contiguous chunk of memory a process can own is 65,536 bytes. For applications that need larger data structures, OS/2 offers a set of "huge memory block" functions to manage areas of logically contiguous storage spanning multiple segments. A huge block is initially allocated with the function DosAllocHuge. Two of the parameters for this function are the same as those for DosAllocSeg: the flags word controlling discardability and sharing, and the address of a variable to receive a selector. The other parameters are the number of full 64 KB segments to allocate, the length in bytes (which may be zero) of an additional partial segment, and the maximum size (in 64 KB segments) to which the process can resize the block at some later time. If the DosAllocHuge call succeeds, the component segments of the huge block are assigned predictable, sequential selectors. The memory within the block can therefore be smoothly addressed by incrementing the offset until a segment is exhausted and then performing special arithmetic on the selector, using an incrementing value supplied by the operating system to move to the next segment. The incrementing value is obtained with the function DosGetHugeShift, which returns a shift count to be applied to the value 1. For example, if DosGetHugeShift returns 4, shifting the value 1 left by four bit positions yields the increment 10H (16). If DosAllocHuge was called to allocate 224 KB of memory and returned the base selector 017FH for the huge memory block, the entire area would be addressed as follows: Block Range Selector Offsets 063 KB 017FH 0FFFFH 64127 KB 018FH 0FFFFH 128191 KB 019FH 0FFFFH 192223 KB 01AFH 07FFFH DosGetHugeShift need be called only once and can be called at any time during the execution of the application. The value it returns does not change while the application is running. The shift count can also be obtained from the global information segment (see DosGetInfoSeg) or from a special loadtime dynlink fixup for the symbol Huge_Shift. Addressing of huge blocks must be performed with care to avoid generating invalid selectors, wrapping a segment, or overrunning a partial last segment; such errors cause a GP fault that terminates the application. Figure 11-8 contains a simple example of the address arithmetic that would be necessary in an actual program. nsegs equ 3 ; number of complete ; segments in huge block nbytes equ 8000h ; number of bytes in ; partial last segment nmax equ 4 ; maximum size of huge ; block in segments sel dw ? ; receives base selector ; for huge block shift dw ? ; receives shift count ; for selector increment . . . ; get selector increment... push ds ; receives shift count push offset DGROUP:shift call DosGetHugeShift ; transfer to OS/2 or ax,ax ; did we get shift count? jnz error ; jump if function failed . . . ; now allocate huge block... push nsegs ; number of complete segments push nbytes ; bytes in last segment push ds ; receives base selector push offset DGROUP:sel push nmax ; potential max segments push 0 ; 0 = segment not shared call DosAllocHuge ; transfer to OS/2 or ax,ax ; was allocation successful? jnz error ; jump if function failed . . . ; convert shift count to ; actual increment... mov bx,1 ; 1 will be shifted left mov cx,shift ; count from DosGetHugeShift shl bx,cl ; BX <- selector increment mov es,sel ; load base selector mov dx,nsegs ; total segment count mov ax,-1 ; load initializing value cld ; safety first label1: ; initialize a segment... mov cx,8000h ; words per segment xor di,di ; start at offset zero rep stosw ; fill the segment mov cx,es ; increment selector add cx,bx ; for next segment mov es,cx dec dx ; another segment? jnz label1 ; yes, loop ; fill partial segment... mov cx,nbytes ; length of partial segment xor di,di ; start at offset zero rep stosb ; fill the segment . . ; other processing here . ; release the huge block... push sel ; base selector for block call DosFreeSeg ; transfer to OS/2 or ax,ax ; was release successful? jnz error ; jump if function failed . . . Figure 11-8. Allocating a 224 KB huge memory block with DosAllocHuge, filling it with -1s, and then releasing it. To change the size of a huge memory block after it is allocated, use DosReallocHuge. This function is called with the base selector obtained from DosAllocHuge, the new number of 64 KB segments, and the length in bytes of any partial last segment. You can always reduce the size of a huge block, but you cannot increase its size beyond the maximum specified when the block was created. Such an attempt fails because OS/2 was not warned to reserve a sufficient number of consecutive selectors. Of course, a "grow" operation might also fail for the various other reasons described above for DosReallocSeg. A process can release a huge memory block by terminating or by calling DosFreeSeg with the base selector. All segments of a huge block are deallocated at the same time. Discardable Segments A discardable segment is created by setting bit 2 of the flags parameter for a call to DosAllocSeg or DosAllocHuge. Discardable segments are useful for tables and other data that can be regenerated quickly on demand. Such a segment might also be employed for infrequently used static data that is loaded from a filethe overhead of writing the data to a swap file and finding swap space (to duplicate data that already exists in the original file) is thereby avoided. When a segment is allocated with the discardable bit set in the flags word, OS/2 initially considers the segment to be swappable and movable, but not discardable. The program notifies OS/2 that the segment is discardable by calling DosUnlockSeg with the selector for the segment. Subsequently, when a low memory situation arises, OS/2 throws away any unlocked, discardable segments on an LRU basis before it resorts to swapping nondiscardable segments. In the case of a huge memory block, all the segments that comprise the block are discarded together. When a program needs to access an unlocked, discardable segment, it first calls DosLockSeg with the segment's selector (or the base selector, in the case of a huge block). If the contents of the segment are still valid, the DosLockSeg call is successful (AX = 0); if the segment has been discarded, an error code is returned. In the latter case, the process must reallocate the segment to its original size with DosReallocSeg or DosReallocHuge (which also perform an implicit DosLockSeg) and then rebuild or reload the contents of the segment. Figure 11-9 contains an example of this process. DosLockSeg and DosUnlockSeg calls can be nested. If DosLockSeg is called multiple times to lock a segment, DosUnlockSeg must be called the same number of times before the segment becomes discardable again. OS/2 maintains only an 8-bit lock counter for each segment; if the count reaches 255 for a segment, the segment becomes permanently locked and subsequent calls to DosLockSeg or DosUnlockSeg have no effect. sel dw ? ; receives selector . . . ; allocate discardable global segment... push 8000h ; segment is 32 KB long push ds ; variable receives selector push offset DGROUP:sel push 4 ; 4 = discardable, ; not shareable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was allocation successful? jnz error ; jump if function failed call rtable ; read the data table into the segment ; now unlock segment... push sel ; selector from DosAllocSeg call DosUnlockSeg ; transfer to OS/2 or ax,ax ; was unlock successful? jnz error ; jump if function failed . . ; other processing here... . ; now lock segment so we can use data table... push sel ; selector from DosAllocSeg call DosLockSeg ; transfer to OS/2 or ax,ax ; was lock successful? jz label1 ; jump if segment locked ; segment was discarded, must reallocate it... push 8000h ; size is 32 KB push sel ; selector for segment call DosReallocSeg ; transfer to OS/2 or ax,ax ; was resize successful? jnz error ; jump if function failed call rtable ; refresh segment by rereading data table label1: ; segment is now locked... . . ; use contents of segment... . ; unlock segment again... push sel ; selector from DosAllocSeg call DosUnlockSeg ; transfer to OS/2 or ax,ax ; was unlock successful? jnz error ; jump if function failed . . . Figure 11-9. Allocating a discardable segment to hold a table of data, unlocking the segment so that it can be discarded in low memory situations, locking the segment when its contents are needed, and reallocating and refreshing the segment if it was discarded. Assume that the subroutine rtable (not shown) reads the table of data from a disk file into the memory block whose selector is in the variable sel. Writable Code Segments On occasion, a program might want to load or dynamically create executable code in a read/write data segment and then branch to that code. Unfortunately, because a particular selector can refer to either executable code or data but not both, loading a selector for a data segment into the CS register by execution of a far JMP or CALL results in a GP fault and process termination. The solution is to allocate two selectors for the same piece of physical memory: a read/write data selector and an executable (text) selector. The function DosCreateCSAlias exists specifically for this purpose: It returns an executable "alias" for a data segment currently owned by a process. This function makes it easy to write incremental compilers and other self-extending systems for OS/2. A basic strategy to use for incremental compilers is as follows: 1. Allocate a nonshared, nondiscardable data segment that is large enough to hold the program's original machine code and any additional machine code that will be compiled during program execution. 2. Copy the current contents of the text segment to the new data segment. 3. Obtain a CS alias for the new data segment and transfer control to the new segment. 4. Release the original text segment and continue execution from the new segment, which has both text and read/write data selectors. Figure 11-10 contains skeleton code for these four steps. Programs which need to execute the generated code only once can use other approaches. For example, some Presentation Manager graphics modules build machine code on the stack, obtain a CS alias for the stack segment, call the new code, and then discard the code immediately. dsel dw ? ; receives data selector xoffs dw offset label1 ; offset of jump target xsel dw ? ; executable selector tsel dw seg _TEXT ; selector for original ; executable (text) segment . . . ; allocate global segment... push 0 ; segment size = 64 KB push ds ; variable receives selector push offset DGROUP:dsel push 0 ; 0 = not shareable ; or discardable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was allocation successful? jnz error ; jump if function failed ; get executable alias... push dsel ; selector from DosAllocSeg push ds ; receives executable alias push offset DGROUP:xsel call DosCreateCSAlias; transfer to OS/2 or ax,ax ; was function successful? jnz error ; jump if no alias available ; copy old text segment ; to new segment... mov es,dsel ; read/write selector mov cx,pgmlen ; length of program xor si,si ; initialize 'from' offset xor di,di ; initialize 'to' offset ; move the program code rep movs byte ptr es:[di],byte ptr cs:[si] jmp dword ptr xoffs ; transfer control to ; the new text segment label1: ; now we are executing from ; the new text segment... ; release old text segment push tsel ; segment selector call DosFreeSeg ; transfer to OS/2 or ax,ax ; was release successful? jnz error ; jump if function failed . . . pgmlen equ $ ; get length of text segment ; at end of program Figure 11-10. Allocating a new data segment, obtaining a CS alias for that segment, and continuing execution from the new segment. Local Storage Dynamic allocation of local storage is a standard feature of C function libraries; the portion of a program's private memory set aside for this purpose is called the local heap. C application programs can dynamically allocate, resize, and release small blocks from the local heap for arrays and buffers with the library functions malloc(), realloc(), and free(), respectively. This technique allows much more efficient use of memory than statically preallocating all the data structures that a program might require only transiently during its execution. OS/2 supports three functions that manage local memory pools (analogous to the routines provided by the C runtime library) on behalf of programs written in assembler or any high level language. You can use these functions with any read/write data segment except for DGROUP to which the program has accessshared or unshared. The function DosSubSet initializes the OS/2 control structures for a local memory pool. The calling parameters are a segment selector obtained from DosAllocSeg or DosAllocShrSeg, a flag (1 to initialize the pool), and the heap size in bytes (not necessarily the entire segment). The heap size is always a multiple of 4 bytes, with a minimum of 12 bytes; if the size is not evenly divisible by 4, the system rounds it up. Note that DosSubSet considers a size of zero to be an error, although it is treated as a special case (equivalent to 65,536 bytes) by most other memory functions. The function DosSubAlloc obtains a block of storage from a local memory pool. It is called with the selector of the memory segment that holds the local pool, the desired size in bytes, and the address of a variable to receive the offset of the block. The maximum size block you can allocate is the size of the pool itself less 8 bytes (the length of the OS/2 control structure at the head of the segment). The call to DosSubAlloc fails if the local pool contains insufficient free memory to satisfy the request; in this event, the actual size of the largest available block is returned instead. To release a block back to a local pool, call DosSubFree with the selector of the segment holding the pool, and the offset and size of the block. The function fails if the offset and size parameters do not match those of a block previously allocated with DosSubAlloc. OS/2 does not provide a function analogous to DosReallocSeg that you can use to resize a block of storage obtained with DosSubAlloc. A program can simulate such a function by calling DosSubAlloc to obtain a new block of the desired size, copying the data from the old block into the new block, and releasing the original block. To enlarge the local pool as a whole, make an additional call to DosSubSet and pass it the same selector as in the original call, the new size in bytes, and a flag value of 0. If the local pool already occupies the entire segment, the segment itself must first be expanded with DosReallocSeg. Of course, if the segment size is already 65,536 bytes, neither it nor the size of the local pool can increase further. WARNING: To improve their execution speed, DosSubAlloc and DosSubFree perform minimal error checking. Invalid parameters are not always detected and can produce unpredictable program behavior or a GP fault that terminates the process. Chapter 12 Multitasking Multitasking is the technique of dividing CPU time among programs in such a way that they appear to be running simultaneously. Of course, the processor is executing only one sequence of machine instructions within one task at any given time, but the process of switching from one task to another is invisible to both the user and the programs themselves. (Please accept the imprecise word task for a few paragraphs; specifics will follow.) The operating system allocates memory resources to the various tasks and resolves contending requests for peripheral devices such as video displays and disk drives. The part of the operating system that allocates CPU time among tasks is called the scheduler, and the rotation from one task to another is called a context switch. When a context switch occurs, the scheduler must save the current state of the active task, restore the registers and program counter to their state when the imminent task was last suspended, and then transfer control to that task. Multitasking operating systems use two basic types of schedulers: event-driven and preemptive. Event-driven schedulers rely on each task to be well-behavedto yield control of the processor at frequent enough intervals so that every program has acceptable throughput, and none is starved for CPU cycles. This yielding of control can be explicit (calling a specific operating system function to give up control) or implicit (suspending the program when it requests the operating system to perform I/O on its behalf and regaining control only after the I/O is completed and other tasks have in turn yielded control). The event-driven strategy is efficient in transaction-oriented systems, which perform a great deal of I/O and not much computation, but such a system can be brought to its knees by a single compute-bound task such as an in-memory sort. A preemptive scheduler relies on an external signal generated at regular intervals, typically a hardware interrupt triggered by a programmable timer or clock device. When the interrupt occurs, the operating system gains control from whatever task was executing, saves its context, evaluates the list of programs that are ready to run, and gives control to ("dispatches") the next program. This approach to multitasking is often called "time-slicing," a term based on the mental image of dividing the sweep of a second hand around a clock face into little wedges and doling them out to the eligible programs. The central processors of mainframes and minicomputersand of microcomputers based on the more powerful microprocessors such as the Intel 80286/386 and the Motorola 68020/30include features that make multitasking operating systems more efficient and robust. The two most important of these features are privilege levels and memory protection. In the simplest use of privilege levels, the CPU is in either kernel mode or user mode at any given time. In kernel mode, which is reserved for the operating system, any machine instruction can be executed. As part of the mechanism for transferring control from the operating system to an application program, the CPU is switched into user mode. In this CPU state, execution of certain reserved instructions (such as those that disable interrupts or write to an I/O port) causes a CPU fault and returns control to the operating system. Memory protection, as described in Chapter 11, is the hardware's ability to detect any attempt by an application program to access memory that does not belong to it. Such an access generates a CPU fault, allowing the operating system to regain control and put the wayward task to sleep forever. Multitasking in OS/2 OS/2 is built around a preemptive, priority-based scheduler. To understand multitasking under OS/2, you need to grasp three distinct but related operating system concepts: processes, sessions, and threads (Figure 12-1). The simplest kind of OS/2 process is very similar to an application program loaded for execution under MS-DOS. OS/2 creates a process by allocating memory segments to hold the code, data, and stack for the process and by initializing the memory segments from the contents of the program's EXE disk file. Once it is running, a process can access additional system resources, such as files, pipes, semaphores, queues, and additional memory, with various OS/2 function calls (Figure 12-2 on p. 230). It can start child processes, influence their fate, find out when and why they terminate, and retrieve their exit codes. ͻ Physical Screen, Session Keyboard, and 1 Ŀ Mouse ͼ Ŀ Task switcher //Ŀ ͻ ͻ ͻ Session Session ... Session 1 Ŀ 2 Ŀ n Ŀ ͼ ͼ ͼ Ŀ Ŀ Ŀ Ŀ Ŀ Process A Process B Process C Process D Process E Ŀ Ŀ Ŀ Ŀ Ŀ T T T T T T T T T h h h h h h h h h r r r r r r r r r e e e e e e e e e a a a a a a a a a d d d d d d d d d 1 2 1 1 2 3 1 1 2 Figure 12-1. The elements of multitasking: sessions, processes, and threads. Processes in turn are members of sessions (sometimes called screen groups), which are the average user's perception of OS/2 multitasking. By pressing Ctrl-Esc, the user can exit from the current session to a window displayed by the Task Manager and then select an application already executing in another session or establish a new session with the Program Starter. The user can also cycle directly between sessions by pressing Alt-Esc. OS/2 maintains a separate virtual screen, mouse, and keyboard for each session. The virtual screen receives the output of all processes in the session and is mapped to the physical display whenever the user selects that session using the Task Manager. New processes are added to an existing session when they are launched by a process already executing within the group. The Task Manager can juggle as many as 16 sessions; however, several are dedicated to special processes, such as the swapper and critical error handler, leaving 12 protected mode sessions and 1 real mode session available for applications. Process Ŀ Process ID (PID) Local descriptor table (LDT) System resources owned or used by process Files Pipes Queues System semaphores Device monitors Child sessions and processes Current disk Current directory for each disk Exception and signal handlers Thread 1 Thread 2 Thread n Ŀ Ŀ Ŀ Thread ID = 1 Thread ID = 2 Thread ID = n Priority Priority Priority Thread state Thread state Thread state Blocked Blocked Blocked Ready Ready ... Ready Executing Executing Executing Length of Length of Length of timeslice timeslice timeslice CPU state CPU state CPU state 80x87 state 80x87 state 80x87 state Figure 12-2. Process-specific and thread-specific information maintained by OS/2. The OS/2 scheduler, however, knows nothing about processes or sessions; it distributes the CPU cycles among dispatchable entities known as threads. Each thread has its own priority, stack, point of execution, CPU state, and (optionally) numeric coprocessor state (Figure 12-2 again). The state includes the CPU's flags and the contents of all registers. At any given time, a thread is either blocked (waiting for I/O or some other event), ready to execute, or actually executing. The scheduler can handle a maximum of 255 threads, although the default system-wide limit (controlled by the THREADS directive in CONFIG.SYS) is 64 in OS/2 version 1.0 and 128 in OS/2 version 1.1. Each process starts life with a primary thread (also called thread 1), whose execution begins at the entry point designated in the EXE file header. However, that first thread can start additional threads within the same process, all of which share ownership of the process's resources. Multiple threads within a process execute asynchronously to one another, can have different priorities, and can manipulate one another's priorities. Although the threads within a process have separate stacks, they share the same near data segment (DGROUP) and thus the same local heap. Careful design of your code to use the stack for local variables makes procedures inherently shareable among threads. (This occurs naturally in C programs.) Access to static variables or other data structures must be coordinated between threads through use of semaphores or other synchronization mechanisms (see Chapter 13). How does control of the CPU pass from one thread to another? A clock tick or other hardware interrupt causes a transition to kernel mode, whereupon OS/2 saves the current thread's state and calls the appropriate driver to handle the interrupt. Certain API calls by a thread also result in a switch to kernel mode. When OS/2 is ready to exit kernel mode, the scheduler examines its list of active threads. The thread with the highest priority that is ready to execute gains control of the machine. If the thread that was most recently active is one of multiple eligible threads with the same priority, and if it has not used up its timeslice, it receives preference. If a thread becomes starved for CPU cycles because other threads with higher priorities are getting all the attention, OS/2 temporarily bumps that thread's priority to a higher value (see below: "Configuring the OS/2 Multitasker"). A thread that is currently reading the keyboard also receives a supplemental boost to its priority. The Multitasking API The OS/2 multitasking services available to application programs are summarized in Figure 12-3 on the following page. These services include: Starting and stopping child processes Obtaining the termination type and exit code of a child process Starting, suspending, and destroying threads Altering the priorities of threads Starting, stopping, and selecting new sessions This range of services allows tremendous flexibility in application design. An application can be factored into asynchronously executing components in many different ways: as multiple threads in a single process sharing the same files and memory; as multiple processes sharing the same virtual screen, keyboard, and pointing device; as multiple processes with distinct virtual screens, keyboard, and pointing devicesor any combination! OS/2 Function Description Thread Management DosCreateThread Creates new thread of execution within same process DosEnterCritSec Freezes other threads in same process DosExit Terminates current thread DosExitCritSec Unfreezes other threads in same process DosGetPrty Gets thread priority DosResumeThread Reactivates specific thread DosSetPrty Sets thread priority DosSuspendThread Suspends specific thread Process Management DosCwait Checks child process status DosExecPgm Runs child process DosExit Terminates current process DosExitList Registers routines to be executed at process termination DosGetPID Returns PID of current process and its parent DosGetPPID Returns PID of specified process's parent DosKillProcess Terminates another process DosPTrace Inspects/modifies/traces child process Session Management DosSelectSession Brings session to foreground DosSetSession Sets session status DosStartSession Creates new session DosStopSession Terminates session Figure 12-3. OS/2 multitasking services at a glance. Managing Processes The API function DosExecPgm is used by one process, called the parent, to load and execute another process, called the child. This function is analogous to, but considerably more powerful than, the EXEC function (Int 21H Function 4BH) in MS-DOS versions 2.0 and later. A child process can, in turn, load other processes until some essential system resource is exhausted. The child process inherits access to some of the parent process's resources: the handles for any open files (unless the parent process explicitly opened the files with the no-inheritance flag), handles to any open pipes, the current disk and current directories of the parent, and a copy of the parent's environment (unless the parent goes to the trouble of creating a new block and passes a pointer to it). DosExecPgm is called with the following: The address of the ASCIIZ program pathname A parameter selecting synchronous, asynchronous, or detached execution for the child process Addresses or NULL pointers for an argument string block and environment to be passed to the child process Addresses of buffers to receive the process ID of the child and certain other information The program pathname must include the extension (typically EXE), but the OS/2 loader looks at the file header to determine whether the file is executable and does not deduce that fact from the extension. If a fully qualified pathname (including a drive, path, and filename) is supplied for the child, the loader looks only for the specified file. If only a filename and extension are supplied, the loader looks in each directory named in the process's environment PATH string for a matching file. When a child process executes synchronously, execution of the thread (in the parent process) that made the DosExecPgm call is suspended until the child process terminates (either intentionally or as the result of an error condition). When the thread in the parent resumes execution, it receives the return code of the child and a termination code that indicates whether the child terminated normally or was terminated by a fault, Ctrl-C, Ctrl-Break, and so forth. If the child process is asynchronous, OS/2 returns the child's process ID (PID), and the thread that issued the DosExecPgm call continues to execute while the child is running. A thread in the parent process can later use DosCwait to find out whether the child is still running or to resynchronize with the child process (by suspending itself until the child terminates and then obtaining the child's return code). Alternatively, the parent can use the child's PID with DosKillProcess to terminate unilaterally the execution of the child process (and, optionally, all grandchild processes) at any time. When a child process is started with the detach option, the process is "orphaned" from the parent: DosKillProcess and DosCwait functions issued by the parent or its ancestors do not take notice of or affect detached child processes. The child cannot interact with the user without using VioPopUp. (The interactive DETACH command is implemented with this variation of DosExecPgm.) The system also provides DosExecPgm options for session managers and debuggers; however, their use is beyond the scope of this book (although they are described briefly in the reference section). The environment block, which consists of a series of ASCIIZ strings terminated by an extra zero byte, was discussed in detail in Chapter 3. The parent process can construct a new environment for the child, or it can pass a NULL pointer, in which case the child receives an exact copy of the parent's environment. The block of argument strings supplied to DosExecPgm is similar in form to the environment block. For compatibility with CMD.EXE, the parent should pass the ASCIIZ simple filename of the child (without extension), then the ASCIIZ command tail, followed by another zero byte. In the case of closely coupled child processes that the user would never run directly, the parent typically passes a NULL pointer and communicates its needs to the child by other methods. A DosExecPgm call can fail for many reasons. The two most common are that the specified file cannot be found or that the drive or file containing the program was locked by another process. In a heavily loaded system, a program might not load because the system limits on threads have been reached or because the disk does not hold sufficient swap space. Another, more obscure reason is that the program references a dynlink library or entry point that the system loader can't find; in that case, the name of the missing library and procedure is returned to the parent. Figures 12-4 and 12-5 contain simple examples of the use of DosExecPgm for synchronous and asynchronous execution of CHKDSK as a child process. The options for asynchronous execution provided by DosExecPgm, coupled with the several options available with DosCwait and DosKillProcess, allow for very flexible execution-time relationships between parent and child processes. pname db 'chkdsk.com',0 ; pathname of child ; argument strings... args db 'chkdsk',0 ; simple filename of child db ' *.*',0 ; simulated command tail db 0 ; extra null ends block ; receives return codes retcode dw 0 ; child's termination type dw 0 ; child's DosExit code objname db 64 dup (0) ; receives name of dynlink objname_len equ $-objname ; causing DosExecPgm failure . . . ; run CHKDSK as synchronous ; child process... push ds ; receives module/entry ; point if dynlink fails push offset DGROUP:objname push objname_len ; length of buffer push 0 ; 0 = execute synchronously push ds ; address of argument block push offset DGROUP:args push 0 ; address of environment push 0 ; (0 = inherit parent's) push ds ; receives child's exit ; and termination codes push offset DGROUP:retcode push ds ; pathname of child push offset DGROUP:pname call DosExecPgm ; transfer to OS/2 or ax,ax ; did child process run? jnz error ; jump if function failed . . . Figure 12-4. Using DosExecPgm to run CHKDSK as a synchronous child process. pname db 'chkdsk.com',0 ; pathname of child ; argument strings for child args db 'chkdsk',0 ; simple filename of child db ' *.*',0 ; simulated command tail db 0 ; extra null ends block ; receives DosExecPgm info cpid dw 0 ; PID of child process dw 0 ; (word not used) scratch dw 0 ; PID scratch for DosCwait ; receives DosCwait info retcode dw 0 ; child termination type dw 0 ; child exit code objname db 64 dup (0) ; receives name of dynlink objname_len equ $-objname ; causing DosExecPgm failure . . . ; run CHKDSK as asynchronous ; child process... push ds ; receives module/entry ; point if dynlink fails push offset DGROUP:objname push objname_len ; length of buffer push 2 ; 2 = execute asynchronously push ds ; address of argument block push offset DGROUP:args push 0 ; address of environment push 0 ; (0 = inherit parent's) push ds ; receives child's PID push offset DGROUP:cpid push ds ; name of child program push offset DGROUP:pname call DosExecPgm ; transfer to OS/2 or ax,ax ; was child process started? jnz error ; jump if function failed . . ; other processing here... . ; now resynchronize with ; child process... push 0 ; 0 = immediate child only push 0 ; 0 = wait till child ends push ds ; receives termination info push offset DGROUP:retcode push ds ; receives PID (N/A here) push offset DGROUP:scratch push cpid ; PID to wait for call DosCwait ; transfer to OS/2 . . . Figure 12-5. Using DosExecPgm to run CHKDSK as an asynchronous child process, then calling DosCwait to resynchronize with CHKDSK and retrieve its exit code and termination type. Managing Threads Multiple threads within a process share the memory space and system resources owned by the process and can communicate rapidly using shared data structures. Threads can start, suspend, or die quickly and have low system overhead; they are not visible outside the process and do not require the extensive initialization and cleanup associated with starting or terminating a process. Use of multiple threads is particularly appropriate when a process must manage devices with vastly different I/O rates and remain responsive to the needs of each. A thread starts another thread by calling DosCreateThread and supplying it with the addresses of the initial execution point and stack base of the new thread. (Because the stack grows downward, you must supply the address of the last byte plus 1 in the area allocated for the stack.) If the call to DosCreateThread succeeds, OS/2 returns a thread ID and adds the thread to the scheduler's list. The thread ID is local to the process and, unlike a PID, is not unique within the system. Each thread is initially entered through a far call from OS/2 and can terminate with a far return or by calling DosExit. In the latter case, the thread supplies a parameter that indicates whether the entire process or the thread alone is being terminated. In OS/2 version 1.1, a DosExit call by thread 1 receives special treatment and always terminates the process This is to ensure that signals can be sent to the process and that Exitlist routines can be executed when the process terminates. , whereas in OS/2 1.0 the sole remaining thread in a process need not be thread 1. When a thread has nothing to do, it should block on a semaphore to await reactivation by another thread or process that clears the same semaphore (see Chapter 13). An alternative (and, in most cases, less desirable) approach is for the thread to use DosSleep to block itself for a programmed period of time or simply to give up the remainder of its timeslice. While a thread is blocking, its cost to the system in CPU cycles is nearly zero. Aside from using semaphores, which require cooperation, the threads in a given process can influence one another's execution in several ways. A thread can call DosSuspendThread or DosResumeThread, with the thread ID returned by DosCreateThread, to suspend or reactivate another thread without that thread's knowledge; a thread can protect itself from such interference with DosEnterCritSec and DosExitCritSec. Similarly, a thread can use a thread ID with DosGetPrty or DosSetPrty to inspect or modify its execution priority or that of other threads. Thread priorities fall into three categoriesidle-time, regular, and time-criticalwith 32 levels within each category. The idle-time category is intended for threads that should execute only when the system has nothing else to do; it should never be used for a thread that interacts with the user. The time-critical category is intended for threads that must respond rapidly to some event (usually timer related or I/O related) to preserve system performance; you will see a use of this category in the SNAP program (Chapter 18). The normal priority category applies to routine execution of threads in application programs, and the primary thread of a process is placed into this category by default; additional threads within a process inherit the priority of the thread that created them. Figure 12-6 contains the source code for a simple multithreaded program. The first thread starts up another thread which emits 10 beeps at 1-second intervals, while the first thread waits for a keystroke and terminates the process when one is received. Although this is a trivial use of threading, it gives an inkling of the enormous power of the thread concept and exhibits the ease with which you can incorporate asynchronous processing into an OS/2 application. stksiz equ 2048 ; size of stack for ; new thread . . . cdata db 10 dup (0) ; receives character data ; from KbdCharIn sel dw ? ; receives selector ; from DosAllocSeg tid dw ? ; receives thread ID . . . ; allocate stack segment ; for new thread... push stksiz ; size of new segment push ds ; receives selector push offset DGROUP:sel push 0 ; 0 = segment not shareable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was allocation successful? jnz error ; jump if function failed ; start new thread... push cs ; thread entry point push offset _TEXT:beeper push ds ; receives thread ID push offset DGROUP:tid push sel ; stack for new thread push stksiz call DosCreateThread ; transfer to OS/2 or ax,ax ; was new thread created? jnz error ; jump if function failed ; now wait for key... push ds ; receives data packet push offset DGROUP:cdata push 0 ; 0 = wait for character push 0 ; keyboard handle call KbdCharIn ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 0 ; exit code = 0 (success) call DosExit ; transfer to OS/2 . . . beeper proc far ; thread entry point mov cx,10 ; max of 10 beeps beep1: ; sound a tone... push 440 ; 440 Hz push 100 ; 100 milliseconds call DosBeep ; transfer to OS/2 push 0 ; now suspend thread push 1000 ; for 1 second... call DosSleep ; transfer to OS/2 loop beep1 ; loop 10 times ret ; terminate this ; thread only beeper endp Figure 12-6. A simple demonstration of multithreading. The first thread starts a second thread and then waits for any key to be pressed. When a key is detected, the process is terminated regardless of the state of the second thread. The second thread emits 10 beeps at 1-second intervals and then destroys itself, possibly still leaving the first thread waiting for the keyboard. Managing Sessions The API functions for session control are present mainly to support the Task Manager, but they can also be useful at the application level in special situations. The most important of these functions, DosStartSession, is a sort of big brother to DosExecPgm: It starts a new process and at the same time creates a child session to hold that process. DosStartSession requires the following parameters: The ASCIIZ pathname for the child process to be placed in the new session An ASCIIZ argument string (command tail) for the child process An ASCIIZ session title for the Task Manager menu Parameters that control the behavior and visibility of the child session and process An optional queue name The pathname must be fully qualified (drive, path, filename, and extension of the process to be loaded in the child session); the use of the session title is obvious. The argument string is a single ASCIIZ string, rather than the block of argument strings used by DosExecPgm, and it is not accompanied by a pointer to an environment for the new session and process. The initial process in a session receives an environment that is empty except for a PATH string; it is expected to synthesize an appropriate environment for its children. The new session also starts with the current drive and directory set to the root directory of the system's boot device, rather than inheriting current settings from the process that calls DosStartSession. If a queue name is supplied, OS/2 places a message in the queue when the child session (actually, the initial process in the child session) terminates. (Queues will be explained in Chapter 13.) The message contains the child session's ID and the child process's return code. DosCwait cannot be used with DosStartSession to synchronize with a process in the new session. The other parameters to DosStartSession determine whether the new session starts in the foreground (visible, replacing the parent session) or in the background (not visible, but available on the Task Manager switch list); whether the initial process in the session is traceable (for use by debuggers); and whether it is related to the parent session. When the child session is not related, it runs free, and the parent cannot further affect it. (Incidentally, the START command of CMD.EXE is implemented as a call to DosStartSession with parameters specifying that the new session is in the background and not related.) When the child session is related, the parent can terminate the session and its member processes (and any grandchild sessions and processes) with DosStopSession. Related child sessions and their descendants are also exposed to a hidden danger: They are unilaterally terminated by OS/2 if the parent exits or is terminated unexpectedly. This relationship between parent and child sessions is much different than the relationship that exists between parent and child processes created by DosExecPgm. A parent can also use two other API functions to control related child sessions. If the parent is executing in the foreground, it can make one of its child sessions visible instead with DosSelectSession (and later return itself to visibility in the same manner). Finally, the parent can use DosSetSession to determine whether a related child session is selectable via the Task Manager or to bind itself to a child session. When a parent and child session are bound together, the child session comes to the foreground whenever the child or parent session is selected. Figure 12-7 contains a simple example of a DosStartSession call. The code launches a related child session that contains a copy of CMD.EXE. The argument string for CMD.EXE causes it to display a directory listing and then a prompt. The child session terminates when the user types EXIT or when the parent session terminates or issues a DosStopSession call. ; child session parameters sesdata dw 24 ; length of structure dw 1 ; 0 = unrelated, 1 = related dw 1 ; 0 = foreground, 1 = background dw 0 ; 0 = not traceable, 1 = traceable dd title ; pointer to session title dd pname ; pointer to process name dd args ; pointer to argument string dd 0 ; pointer to queue name ; pathname for child pname db 'c:\os2\pbin\cmd.exe',0 args db ' /k dir/w',0 ; argument string for child ; session title title db 'Child Command Processor',0 cid dw 0 ; receives process ID sid dw 0 ; receives session ID . . . ; run CMD.EXE as child ; in a new session... push ds ; address of session data push offset DGROUP:sesdata push ds ; receives session ID push offset DGROUP:sid push ds ; receives process ID push offset DGROUP:cid call DosStartSession ; transfer to OS/2 or ax,ax ; was session started? jnz error ; jump if function failed . . . Figure 12-7. Using DosStartSession to load CMD.EXE in a new screen group. The command tail passed to CMD.EXE causes it to display a directory listing followed by a prompt. Because the new session is placed in the background, you must switch to it with the Task Manager hot key in order to view the directory. Configuring the OS/2 Multitasker Four optional CONFIG.SYS directives influence OS/2 multitasking: THREADS=n MAXWAIT=seconds PRIORITY=[ABSOLUTE | DYNAMIC] TIMESLICE=x[,y] The THREADS directive controls the number of threads that can be created simultaneously in the system. The parameter n must fall in the range 16 through 255, with a default of 64 in OS/2 version 1.0 and 128 in version 1.1. The upper limit of 255 cannot be expanded. When a thread is denied the CPU for the number of seconds specified by MAXWAIT (because other higher priority threads are monopolizing the timeslices), the "starved" thread receives a temporary increase in priority for one timeslice. This provision ensures that all processes make some progress and eventually recognize events that concern them. The seconds parameter for MAXWAIT must be in the range 1 through 255, with a default value of 3. The PRIORITY directive enables or disables mechanisms that OS/2 uses to alter the priority of each thread dynamicallybased on the activity of other threads within the system. If PRIORITY=DYNAMIC (the default keyword), the priority of threads can be adjusted within a class as a function of their execution history and the visibility of their session. When PRIORITY=ABSOLUTE, thread priorities are not adjusted, and the MAXWAIT directive has no effect. The TIMESLICE directive controls the length of the timeslices that the OS/2 scheduler uses. The x parameter represents the normal length of a thread's timeslice in milliseconds, and y is the maximum length; if TIMESLICE is not present in CONFIG.SYS, both default to 31. If the y parameter is absent, its value defaults to x. If a thread uses up its entire timeslice and must be preempted, its next timeslice is one tick longer, up to the limit set by the y parameter. In this way, OS/2 lets you minimize context-switching overhead when several compute-bound threads are running at the same priority. A Simple Command Interpreter The listings TINYCMD.C (Figure 12-8 on the following page) and TINYCMD.ASM (Figure 12-9 on p. 247) contain the source code for a simple self-contained OS/2 command interpreter. TINYCMD is table-driven so that it can be extended easily. After the user types a command and presses the Enter key, TINYCMD tries to match the first token in the line against its table of internal (intrinsic) commands. This version of TINYCMD has only three intrinsic commands: Command Action CLS Clears the screen EXIT Terminates TINYCMD VER Displays OS/2 version number If a command is not intrinsic, TINYCMD appends .EXE to the first token and attempts to load a program by that name using DosExecPgm. If no program file is found, TINYCMD displays the message Bad command or filename. To add new intrinsic commands to TINYCMD, simply code a routine with the appropriate action, and then add the command text and the name of the associated routine to the table named COMMANDS. You can also prevent the user from running specific extrinsic programs by adding their names to the COMMANDS table and associating them with a routine that displays an error message. To compile TINYCMD.C into the executable file TINYCMD.EXE, type the following command: [C:\] CL TINYCMD.C To assemble TINYCMD.ASM into the file TINYCMD.OBJ, type the following command: [C:\] MASM TINYCMD.ASM; Then use the following command to link TINYCMD.OBJ with the module definition file TINYCMD.DEF (Figure 12-10 on p. 254) and the import library OS2.LIB to produce TINYCMD.EXE: [C:\] LINK TINYCMD,,,OS2,TINYCMD TINYCMD is intended only as a demonstration and would require considerable additional work to approach the functionality of CMD.EXE. You would need to add code to process SET commands and maintain the environment, to interpret batch files, and to search for EXE, COM, and BAT files in that order. You would also need to add a signal handler for Ctrl-C and Ctrl-Break to enable TINYCMD to recover control when the user enters one of these key sequences to terminate a child process. (Signal handlers are discussed in Chapter 13.) /* TINYCMD.C A simple command interpreter for OS/2. Compile with: C> cl tinycmd.c Usage is: C> tinycmd Copyright (C) 1988 Ray Duncan */ #include #include #include /* macro to return number of elements in a structure */ #define DIM(x) (sizeof(x) / sizeof(x[0])) #define INPSIZE 80 /* maximum input length */ #define API unsigned extern far pascal /* OS/2 API prototypes */ API DosExecPgm(char far *, int, int, char far *, char far *, int far *, char far *); API DosGetVersion(char far *); unsigned intrinsic(char *); /* local function prototypes */ void extrinsic(char *); void cls_cmd(void); void ver_cmd(void); void exit_cmd(void); struct _commands { /* intrinsic commands table */ char *name; /* command name */ int (*fxn)(); /* command function */ } commands[] = { "CLS", cls_cmd, /* built-in commands */ "VER", ver_cmd, "EXIT", exit_cmd, }; main(int argc, char *argv[]) { char input[INPSIZE]; /* keyboard input buffer */ while(1) /* main interpreter loop */ { printf("\n>> "); /* display prompt */ memset(input, 0, INPSIZE); /* initialize input buffer */ gets(input); /* get keyboard entry */ strtok(input, " ;,=\t"); /* break out first token */ strupr(input); /* and fold to uppercase */ if(! intrinsic(input)) /* if intrinsic command, run its function */ extrinsic(input); /* else assume EXE file */ } } /* Try to match first token of command line with intrinsic command table. If a match is found, run the associated routine and return true; otherwise, return false. */ unsigned intrinsic(char *input) { int i; /* scratch variable */ for(i=0; i < DIM(commands); i++) /* search command table */ { if(! strcmp(commands[i].name, input)) { (*commands[i].fxn)(); /* if match, run routine */ return(1); /* and return true */ } } return(0); /* no match, return false */ } /* Append .EXE to first token and attempt to execute program, passing address of argument strings block and null pointer for environment (child process inherits TINYCMD's environment). */ void extrinsic(char *input) { char pbuff[INPSIZE]; /* buffer for program name */ unsigned status; /* value from DosExecPgm */ int cinfo[2]; /* child process info */ strcpy(pbuff, input); /* copy first command token to local buffer */ strcat(pbuff, ".EXE"); /* append .EXE to token */ /* try to execute program */ if(DosExecPgm(NULL, /* object name buffer pointer */ 0, /* object name buffer length */ 0, /* synchronous execution mode */ input, /* argument strings */ NULL, /* use parent's environment */ cinfo, /* receives termination info */ pbuff)) /* program name pointer */ printf("\nBad command or filename\n"); } /* These are the subroutines for the intrinsic commands. */ void cls_cmd(void) /* CLS command */ { printf("\033[2J"); /* ANSI escape sequence */ } /* to clear screen */ void ver_cmd(void) /* VER command */ { char verinfo[2]; /* receives version info */ DosGetVersion(verinfo); /* get and display version */ printf("\nOS/2 version %d.%02d\n", verinfo[0], verinfo[1]); } void exit_cmd(void) /* EXIT command */ { exit(0); /* terminate TINYCMD */ } Figure 12-8. TINYCMD.C, the source code for TINYCMD.EXE, a simple command interpreter for OS/2. title TINYCMD -- Simple Command Interpreter page 55,132 .286 ; ; TINYCMD.ASM ; ; A simple command interpreter for OS/2. ; ; Assemble with: C> masm tinycmd.asm; ; Link with: C> link tinycmd,,,os2,tinycmd ; ; Usage is: C> tinycmd ; ; Copyright (C) 1988 Ray Duncan ; cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed tab equ 09h ; ASCII tab blank equ 20h ; ASCII blank code escape equ 01bh ; ASCII escape code inpsize equ 80 ; maximum input length extrn DosExecPgm:far ; OS/2 API functions extrn DosExit:far extrn DosGetVersion:far extrn KbdStringIn:far extrn VioWrtTTY:far DGROUP group _DATA _DATA segment word public 'DATA' ; Intrinsic commands table: Each entry is an ASCIIZ string ; followed by the offset of the corresponding procedure. commands label byte db 'CLS',0 ; clear screen command dw cls_cmd db 'VER',0 ; display OS/2 version dw ver_cmd db 'EXIT',0 ; exit from TINYCMD dw exit_cmd db 0 ; end of table input db (inpsize+2) dup (0) ; keyboard input buffer ibinfo dw inpsize,0 ; input buffer info pbuff db (inpsize+2) dup (0) ; program name moved here cinfo dw 0 ; child termination type dw 0 ; child return code prompt db cr,lf,'>> ' ; TINYCMD's user prompt pr_len equ $-prompt delims db 0,blank,tab,';,=' ; delimiters for first de_len equ $-delims ; command line token pext db '.EXE',0 ; program file extension pe_len equ $-pext wlen dw 0 ; receives bytes written verinfo db 0,0 ; receives OS/2 version msg1 db cr,lf,lf ; error message db 'Bad command or filename' db cr,lf msg1_len equ $-msg1 msg2 db escape,'[2J' ; ANSI escape sequence msg2_len equ $-msg2 ; to clear the screen msg3 db cr,lf,lf ; version number message db 'OS/2 version ' msg3a db 'nn.' msg3b db 'nn' db cr,lf msg3_len equ $-msg3 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP,es:DGROUP main proc far ; entry point from OS/2 push ds ; make DGROUP addressable pop es ; with ES too cld ; clear direction flag main1: ; main interpreter loop call gcmd ; get command from user call intrinsic ; check if intrinsic function jnc main1 ; yes, it was processed call extrinsic ; no, run EXE file, then jmp main1 ; get another command main endp ; Try to match first command token against COMMANDS table. ; If match, run the routine, return Carry = False. ; If no match, return Carry = True. intrinsic proc near ; point to command table mov si,offset DGROUP:commands intr1: ; try next table entry... cmp byte ptr [si],0 ; end of entire table? je intr6 ; jump if table exhausted mov di,offset DGROUP:input ; point to user's command intr2: mov al,[si] ; next character from table or al,al ; end of table entry? jz intr3 ; yes, jump cmp al,[di] ; compare to input character jnz intr4 ; jump, found mismatch inc si ; advance string pointers inc di jmp intr2 intr3: cmp byte ptr [di],0 ; user's entry same length? jne intr5 ; no, not a match call word ptr [si+1] ; run the command routine clc ; return Carry = False ret ; as success signal intr4: lodsb ; look for end of this or al,al ; command string (null byte) jnz intr4 ; not end yet, loop intr5: add si,2 ; skip over routine address jmp intr1 ; try to match next command intr6: stc ; command not matched, exit ret ; with Carry = True intrinsic endp ; Append .EXE to first token and attempt to execute program, ; passing address of argument strings block and null pointer ; for environment. (Child process inherits TINYCMD's environment.) extrinsic proc near mov si,offset DGROUP:input ; copy first token of mov di,offset DGROUP:pbuff ; user command to pbuff extr1: lodsb ; get next character or al,al ; found null? jz extr2 ; yes, end of token stosb ; no, copy character jmp extr1 ; and get next character extr2: mov si,offset DGROUP:pext ; append .EXE extension mov cx,pe_len ; to token, forming rep movsb ; program filename ; try to run program... push 0 ; object name buffer push 0 push 0 ; object name buffer length push 0 ; synchronous execution mode push ds ; address of argument strings push offset DGROUP:input push 0 ; environment pointer push 0 push ds ; receives termination info push offset DGROUP:cinfo push ds ; address of program name push offset DGROUP:pbuff call DosExecPgm ; transfer to OS/2 or ax,ax ; was function successful? jz extr3 ; yes, jump ; no, display error message... push ds ; message address push offset DGROUP:msg1 push msg1_len ; message length push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 extr3: ret ; back to caller extrinsic endp ; Get input from user, convert it to argument strings block ; by breaking out first token with null and appending double ; null to entire line, and convert first token to uppercase. gcmd proc near ; prompt user, get command ; display TINYCMD prompt... push ds ; address of prompt push offset DGROUP:prompt push pr_len ; length of prompt push 0 ; default video handle call VioWrtTTY mov ibinfo+2,0 ; disable buffer editing ; get entry from user... push ds ; address of input buffer push offset DGROUP:input push ds ; input buffer info push offset DGROUP:ibinfo push 0 ; 0 = wait for input push 0 ; default keyboard handle call KbdStringIn mov cx,inpsize ; look for carriage return mov al,cr ; if any... mov di,offset DGROUP:input repnz scasb jnz gcmd1 dec di gcmd1: mov word ptr [di],0 ; and replace with 2 nulls mov si,offset DGROUP:input ; break out first token... gcmd2: lodsb ; compare next character mov di,offset DGROUP:delims ; to delimiter table mov cx,de_len repnz scasb jnz gcmd2 ; jump, no match mov byte ptr [si-1],0 ; replace delimiter with 0 mov si,offset DGROUP:input ; fold token to uppercase gcmd3: cmp byte ptr [si],0 ; end of token? jz gcmd5 ; found end, jump cmp byte ptr [si],'a' jb gcmd4 ; jump if not 'a-z' cmp byte ptr [si],'z' ja gcmd4 ; jump if not 'a-z' sub byte ptr [si],'a'-'A' ; convert to uppercase gcmd4: inc si ; go to next character jmp gcmd3 gcmd5: ret ; back to caller gcmd endp cls_cmd proc near ; intrinsic CLS command ; send ANSI escape sequence ; to clear the screen... push ds ; escape sequence address push offset DGROUP:msg2 push msg2_len ; escape sequence length push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 ret ; back to caller cls_cmd endp ver_cmd proc near ; intrinsic VER command ; get OS/2 version number... push ds ; receives version info push offset DGROUP:verinfo call DosGetVersion ; transfer to OS/2 ; format the version... mov al,verinfo ; convert minor version aam ; number to ASCII add ax,'00' xchg al,ah mov word ptr msg3b,ax ; store ASCII characters mov al,verinfo+1 ; convert major version aam ; number to ASCII add ax,'00' xchg al,ah mov word ptr msg3a,ax ; store ASCII characters ; display version number... push ds ; message address push offset DGROUP:msg3 push msg3_len ; message length push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 ret ; back to caller ver_cmd endp exit_cmd proc near ; intrinsic EXIT Command ; final exit to OS/2... push 1 ; terminate all threads push 0 ; return code = 0 call DosExit ; transfer to OS/2 exit_cmd endp _TEXT ends end main ; defines entry point Figure 12-9. TINYCMD.ASM, the source code for TINYCMD.EXE, a simple command interpreter for OS/2. NAME TINYCMD WINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 12-10. TINYCMD.DEF, the module definition file for TINYCMD.ASM. A Simple Multithreaded Application The listings DUMBTERM.C (Figure 12-11) and DUMBTERM.ASM (Figure 12-12, which begins on p. 260) contain the source code for a simple multithreaded application. DUMBTERM is a primitive terminal emulator that exchanges characters between the console (keyboard and video display) and the serial port. The user enters Alt-X to terminate the program. DUMBTERM demonstrates the benefits of using multiple threads when a process must perform I/O to two or more devices. It creates one thread to read the serial port and write the resulting characters to the display, and it creates another thread to read the keyboard and write the resulting characters to the serial port. The threads run independently and block when no input is available, thus conserving CPU cycles and eliminating the need for polling. For simplicity, DUMBTERM does not include any logic for selecting or configuring the serial port based on command line switches or menus. DUMBTERM always opens COM1 and configures it to 2400 baud, 8 bits, no parity, and 1 stop bit. To modify the settings, you must edit the #define or EQU statements at the beginning of the source code. To compile DUMBTERM.C into the executable file DUMBTERM.EXE, type the following command: [C:\] CL /F 2000 DUMBTERM.C The /F option creates a large stack for DUMBTERM's primary thread so that the stacks for the two additional I/O threads can be allocated as automatic arrays within the main stack. You can also allocate new segments for the thread stacks, but you are then more likely to run into problems with the stack-checking routine in the C runtime library. To assemble DUMBTERM.ASM into the file DUMBTERM.OBJ, type the following command: [C:\] MASM DUMBTERM.ASM; Then, to produce DUMBTERM.EXE, link DUMBTERM.OBJ, the import library OS2.LIB, and DUMBTERM.DEF (Figure 12-13 on p. 269), with the following command: [C:\] LINK DUMBTERM,,,OS2,DUMBTERM /* DUMBTERM.C A simple multithreaded OS/2 terminal program that exchanges characters between the console and the serial port. Communication parameters for COM1 are set at 2400 baud, 8 data bits, no parity, and 1 stop bit. Compile with: C> cl /F 2000 dumbterm.c Usage is: C> dumbterm Press Alt-X to terminate the program. Copyright (C) 1988 Ray Duncan */ #define WAIT 0 /* parameters for KbdCharIn */ #define NOWAIT 1 #define EXITKEY 0x2d /* Exit key = Alt-X */ #define STKSIZE 2048 /* stack size for threads */ #define COMPORT "COM1" /* COM port configuration */ #define BAUD 2400 /* 110, 150 ... 19200 */ #define PARITY 0 /* 0=N, 1=O, 2=E, 3=M, 4=S */ #define DATABIT 8 /* 5-8 allowed */ #define STOPBIT 0 /* 0=1, 1=1.5, 2=2 */ #define API unsigned extern far pascal /* OS/2 API prototypes */ API DosClose(unsigned); API DosCreateThread(void (far *)(), unsigned far *, void far *); API DosDevIOCtl(void far *, void far *, unsigned, unsigned, unsigned); API DosExit(unsigned, unsigned); API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long, unsigned, unsigned, unsigned, unsigned long); API DosRead(unsigned, void far *, int, unsigned far *); API DosSemClear(unsigned long far *); API DosSemSet(unsigned long far *); API DosSemWait(unsigned long far *, unsigned long); API DosSuspendThread(unsigned); API DosWrite(unsigned, void far *, int, unsigned far *); API KbdCharIn(void far *, unsigned, unsigned); API KbdGetStatus(void far *, unsigned); API KbdSetStatus(void far *, unsigned); API VioWrtTTY(char far *, int, unsigned); void far comin(void); /* local function prototypes */ void far comout(void); void errexit(char *); struct _F41Info { /* DosDevIOCtl info structure */ int BaudRate; /* 110, 150 ... 19200 */ } F41Info; struct _F42Info { /* DosDevIOCtl info structure */ char DataBits; /* character length 5-8 bits */ char Parity; /* 0=N, 1=O, 2=E, 3=Mark, 4=Space */ char StopBits; /* 0=1, 1=1.5, 2=2 stop bits */ } F42Info; struct _KbdInfo { /* KbdCharIn info structure */ char CharCode; /* ASCII character code */ char ScanCode; /* keyboard scan code */ char Status; /* miscellaneous status flags */ char Reserved1; /* reserved byte */ unsigned ShiftState; /* keyboard shift state */ long TimeStamp; /* character timestamp */ } KbdInfo; struct _KbdStatus { /* KbdGetStatus info structure */ int Length; /* length of structure */ unsigned Mask; /* keyboard state flags */ char TurnAround[2]; /* logical end-of-line */ unsigned Interim; /* interim character flags */ unsigned ShiftState; /* keyboard shift state */ } KbdStatus; unsigned chandle; /* handle for COM port */ unsigned long exitsem; /* Alt-X exit semaphore */ main() { unsigned action; /* result of DosOpen */ unsigned openflag = 0x01; /* fail if device not found */ unsigned openmode = 0x12; /* read/write, deny all */ unsigned cominID; /* COM input thread ID */ unsigned comoutID; /* COM output thread ID */ char cominstk[STKSIZE]; /* COM input thread stack */ char comoutstk[STKSIZE]; /* COM output thread stack */ /* open COM port */ if(DosOpen(COMPORT, &chandle, &action, 0L, 0, openflag, openmode, 0L)) errexit("\nCan't open COM device.\n"); F41Info.BaudRate = BAUD; /* configure COM port */ F42Info.DataBits = DATABIT; /* using DosDevIOCtl */ F42Info.Parity = PARITY; /* Category 1 functions */ F42Info.StopBits = STOPBIT; if(DosDevIOCtl(NULL, &F41Info, 0x41, 1, chandle)) errexit("\nCan't set baud rate.\n"); if(DosDevIOCtl(NULL, &F42Info, 0x42, 1, chandle)) errexit("\nCan't configure COM port.\n"); KbdStatus.Length = 10; /* force keyboard */ KbdGetStatus(&KbdStatus, 0); /* into binary mode */ KbdStatus.Mask &= 0xfff0; /* with echo off */ KbdStatus.Mask |= 0x06; KbdSetStatus(&KbdStatus, 0); exitsem = 0L; /* initialize and */ DosSemSet(&exitsem); /* set exit semaphore */ /* create COM input thread */ if(DosCreateThread(comin, &cominID, cominstk+STKSIZE)) errexit("\nCan't create COM input thread.\n"); /* create COM output thread */ if(DosCreateThread(comout, &comoutID, comoutstk+STKSIZE)) errexit("\nCan't create COM output thread.\n"); puts("\nCommunicating..."); /* sign-on message */ DosSemWait(&exitsem, -1L); /* wait for exit signal */ DosSuspendThread(cominID); /* freeze COM input and */ DosSuspendThread(comoutID); /* output threads */ puts("\nTerminating..."); /* sign-off message */ DosExit(1, 0); /* terminate all threads */ /* return code = 0 */ } /* The 'comin' thread reads characters one at a time from the COM port and sends them to the display. */ void far comin(void) { unsigned rlen; /* scratch variable */ char inchar; /* character input buffer */ while(1) { /* read character from COM */ DosRead(chandle, &inchar, 1, &rlen); VioWrtTTY(&inchar, 1, 0); /* send character to display */ } } /* The 'comout' thread reads characters one at a time from the keyboard and sends them to the COM port. If the exit key is detected, the main thread is signaled to clean up and exit. */ void far comout(void) { unsigned wlen; /* scratch variable */ while(1) { KbdCharIn(&KbdInfo, WAIT, 0); /* read keyboard */ wlen = 1; /* assume 1-byte character */ /* extended character? */ if((KbdInfo.CharCode == 0) || (KbdInfo.CharCode == 0xe0)) { wlen = 2; /* yes, set length = 2 */ if(KbdInfo.ScanCode == EXITKEY) { /* if exit key detected */ DosSemClear(&exitsem); /* signal thread 1 to exit */ wlen = 0; /* and discard character */ } } /* write COM port */ DosWrite(chandle, &KbdInfo.CharCode, wlen, &wlen); } } /* Common error exit routine; displays error message and terminates process. */ void errexit(char *msg) { VioWrtTTY(msg, strlen(msg), 0); /* display error message */ DosExit(1, 1); /* terminate, exit code = 1 */ } Figure 12-11. DUMBTERM.C, the source code for DUMBTERM.EXE, a simple terminal emulator that illustrates use of multiple threads. title DUMBTERM -- OS/2 Terminal Program page 55,132 .286 ; ; DUMBTERM.ASM ; ; A simple multithreaded OS/2 terminal program that exchanges ; characters between the console and the serial port. ; ; Communication parameters for COM1 are set at 2400 baud, ; 8 data bits, no parity, and 1 stop bit. ; ; Assemble with: C> masm dumbterm.asm; ; Link with: C> link dumbterm,,,os2,dumbterm ; ; Usage is: C> dumbterm ; ; Press Alt-X to terminate the program. ; ; Copyright (C) 1988 Ray Duncan ; cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed kwait equ 0 ; KbdCharIn parameters knowait equ 1 stksize equ 2048 ; stack size for threads exitkey equ 2dh ; Alt-X key is exit signal ; COM port configuration com1 equ 1 ; nonzero if using COM1 com2 equ 0 ; nonzero if using COM2 com3 equ 0 ; nonzero if using COM3 baud equ 2400 ; baud rate for COM port parity equ 0 ; 0=N, 1=O, 2=E, 3=M, 4=S databit equ 8 ; 5-8 stopbit equ 0 ; 0=1, 1=1.5, 2=2 extrn DosAllocSeg:far ; OS/2 API functions extrn DosClose:far extrn DosCreateThread:far extrn DosDevIOCtl:far extrn DosExit:far extrn DosOpen:far extrn DosRead:far extrn DosSemClear:far extrn DosSemSet:far extrn DosSemWait:far extrn DosSuspendThread:far extrn DosWrite:far extrn KbdCharIn:far extrn KbdGetStatus:far extrn KbdSetStatus:far extrn VioWrtTTY:far DGROUP group _DATA _DATA segment word public 'DATA' cname label byte ; COM port logical name if com1 db 'COM1',0 ; COM1 device endif if com2 db 'COM2',0 ; COM2 device endif if com3 db 'COM3',0 ; COM3 device endif f41info dw baud ; baud rate f42info db databit ; data bits db parity ; parity db stopbit ; stop bits kbdinfo db 0 ; character code db 0 ; scan code db 0 ; status flags db 0 ; reserved dw 0 ; shift state dd 0 ; timestamp kbdstat dw 10 ; length of structure dw 0 ; keyboard state flags db 0,0 ; logical end-of-line dw 0 ; interim character flags dw 0 ; shift state exitsem dd 0 ; exit semaphore chandle dw 0 ; receives COM device handle action dw 0 ; receives DosOpen action sel dw 0 ; receives selector rlen dw 0 ; receives bytes read count wlen dw 0 ; receives bytes written count cinID dw 0 ; COM input thread ID coutID dw 0 ; COM output thread ID inchar db 0 ; input character from COM port msg1 db cr,lf db "Can't open COM device." db cr,lf msg1_len equ $-msg1 msg2 db cr,lf db "Can't set baud rate." db cr,lf msg2_len equ $-msg2 msg3 db cr,lf db "Can't configure COM port." db cr,lf msg3_len equ $-msg3 msg4 db cr,lf db "Memory allocation failure." db cr,lf msg4_len equ $-msg4 msg5 db cr,lf db "Can't create COM input thread." db cr,lf msg5_len equ $-msg5 msg6 db cr,lf db "Can't create COM output thread." db cr,lf msg6_len equ $-msg6 msg7 db cr,lf db "Communicating..." db cr,lf msg7_len equ $-msg7 msg8 db cr,lf db "Terminating..." db cr,lf msg8_len equ $-msg8 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 ; open COM port... push ds ; device name address push offset DGROUP:cname push ds ; receives device handle push offset DGROUP:chandle push ds ; receives action push offset DGROUP:action push 0 ; initial allocation (N/A) push 0 push 0 ; attribute (N/A) push 1 ; open flag: fail if device ; does not exist push 12h ; open mode: read/write, ; deny-all push 0 ; DWORD reserved push 0 call DosOpen ; transfer to OS/2 or ax,ax ; was function successful? jz main1 ; yes, jump mov cx,msg1_len ; no, display error message mov dx,offset DGROUP:msg1 jmp main9 ; and exit main1: ; set baud rate... push 0 ; data buffer address push 0 push ds ; parameter buffer address push offset DGROUP:f41info push 41h ; function number push 1 ; category number push chandle ; COM device handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; was function successful? jz main2 ; yes, jump mov cx,msg2_len ; no, display error message mov dx,offset DGROUP:msg2 jmp main9 ; and exit main2: ; configure parity, stop bits, ; and character length... push 0 ; data buffer address push 0 push ds ; parameter buffer address push offset DGROUP:f42info push 42h ; function number push 1 ; category number push chandle ; COM device handle call DosDevIOCtl ; transfer to OS/2 or ax,ax ; was function successful? jz main3 ; yes, jump mov cx,msg3_len ; no, display error message mov dx,offset DGROUP:msg3 jmp main9 ; and exit main3: ; put keyboard in binary mode ; with echo off... ; get keyboard state push ds ; address of info structure push offset DGROUP:kbdstat push 0 ; default keyboard handle call KbdGetStatus ; transfer to OS/2 ; set binary mode, no echo and word ptr kbdstat+2,0fff0h or word ptr kbdstat+2,6 ; set keyboard state push ds ; address of info structure push offset DGROUP:kbdstat push 0 ; default keyboard handle call KbdSetStatus ; transfer to OS/2 push ds ; set exit semaphore push offset DGROUP:exitsem call DosSemSet ; transfer to OS/2 ; allocate thread stack push stksize ; stack size in bytes push ds ; receives new selector push offset DGROUP:sel push 0 ; not shareable/discardable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was function successful? jz main4 ; yes, jump mov cx,msg4_len ; no, display error message mov dx,offset DGROUP:msg4 jmp main9 ; and exit main4: ; create COM input thread push cs ; thread entry point push offset _TEXT:comin push ds ; receives thread ID push offset DGROUP:cinID push sel ; address of stack base push stksize call DosCreateThread ; transfer to OS/2 or ax,ax ; was function successful? jz main5 ; yes, jump mov cx,msg5_len ; no, display error message mov dx,offset DGROUP:msg5 jmp main9 ; and exit main5: ; allocate thread stack push stksize ; stack size in bytes push ds ; receives new selector push offset DGROUP:sel push 0 ; not shareable/discardable call DosAllocSeg ; transfer to OS/2 or ax,ax ; was function successful? jz main6 ; yes, jump mov cx,msg4_len ; no, display error message mov dx,offset DGROUP:msg4 jmp main9 ; and exit main6: ; create COM output thread... push cs ; thread entry point push offset _TEXT:comout push ds ; receives thread ID push offset DGROUP:coutID push sel ; address of stack base push stksize call DosCreateThread ; transfer to OS/2 or ax,ax ; was function successful? jz main7 ; yes, jump mov cx,msg6_len ; no, display error message mov dx,offset DGROUP:msg6 jmp main9 ; and exit main7: ; display "Communicating..." push ds ; message address push offset DGROUP:msg7 push msg7_len ; message length push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 ; wait for exit signal push ds ; semaphore handle push offset DGROUP:exitsem push -1 ; wait indefinitely push -1 call DosSemWait ; transfer to OS/2 ; suspend COM input thread push cinID ; thread ID call DosSuspendThread ; transfer to OS/2 ; suspend COM output thread push coutID ; thread ID call DosSuspendThread ; transfer to OS/2 ; display "Terminating"... push ds ; message address push offset DGROUP:msg8 push msg8_len ; message length push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 0 ; return code = 0 (success) call DosExit ; transfer to OS/2 main9: ; display error message... ; DS:DX = msg, CX = length push ds ; address of message push dx push cx ; length of message push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 1 ; return code = 1 (error) call DosExit ; transfer to OS/2 main endp ; COM input thread: Reads characters one at a time ; from the COM port and writes them to the display. comin proc far ; read character from COM... push chandle ; COM device handle push ds ; receives COM data push offset DGROUP:inchar push 1 ; length to read push ds ; receives read count push offset DGROUP:rlen call DosRead ; transfer to OS/2 ; send character to display... push ds ; address of character push offset DGROUP:inchar push 1 ; length to write push 0 ; default video handle call VioWrtTTY ; transfer to OS/2 jmp comin ; wait for next character comin endp ; COM output thread: Reads characters from the keyboard ; in raw mode and sends them to the COM port. If the ; exit key is detected, the main thread is signaled to ; clean up and terminate the process. comout proc far ; read keyboard character... push ds ; receives keyboard data push offset DGROUP:kbdinfo push kwait ; wait if necessary push 0 ; default keyboard handle call KbdCharIn ; transfer to OS/2 mov cx,1 ; assume writing one byte cmp kbdinfo,0 ; check for extended key jz comout1 ; jump, extended key cmp kbdinfo,0e0h jnz comout2 ; jump, not extended key comout1: ; extended key detected mov cx,2 ; must write 2 bytes ; check for exit key cmp kbdinfo+1,exitkey jnz comout2 ; not exit key, jump ; clear exit semaphore... push ds ; semaphore address push offset DGROUP:exitsem call DosSemClear ; transfer to OS/2 jmp comout ; discard exit key comout2: ; send character to COM port... push chandle ; COM device handle push ds ; address of character push offset DGROUP:kbdinfo push cx ; length to write push ds ; receives write count push offset DGROUP:wlen call DosWrite ; transfer to OS/2 jmp comout ; wait for next key comout endp _TEXT ends end main Figure 12-12. DUMBTERM.ASM, the source code for DUMBTERM.EXE, a simple terminal application that illustrates use of multiple threads. NAME DUMBTERM NOTWINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 12-13. DUMBTERM.DEF, the module definition file for DUMBTERM.ASM. Chapter 13 Interprocess Communication The preceding chapter discussed the OS/2 multitasking services that applications use to create multiple threads within a process or to create child processes. However, as we have seen, the multitasking functions provide threads and processes with only limited control over one another. A thread can suspend or change the priority of another thread; it can also prevent other threads from interfering with its execution. A parent process can pass information to a child at load time, obtain the child's exit code and termination type, or unilaterally kill a child process. OS/2's Interprocess Communication (IPC) functions supplement the multitasking functions with mechanisms for the exchange of all kinds of information between threads and processes. The IPC functions fall into five categories: For a cogent, highly readable survey of the state of the IPC art, see Chapter 2 of Operating Systems: Design and Implementation by Andrew S. Tanenbaum, published by Prentice-Hall Inc., Englewood Cliffs, New Jersey, 1987. Semaphores Pipes Shared memory Queues Signals When the OS/2 LAN Manager is running, an additional IPC mechanism called mailslots is also available (not discussed in this book). While reading about IPC functions, bear in mind the distinctions between processes and threads that were explained in Chapter 12. When a process opens or creates a semaphore, pipe, queue, or shared memory segment, the system returns a handle or selector that any thread within that process can use. But when a thread issues an OS/2 function call that blocks on (waits for) an event (such as the clearing or release of a semaphore) or performs a synchronous read or write to a pipe or queue, only the calling thread is suspendedother threads in the process continue to run as before. Semaphores Think of a semaphore as a simple object with two states: set (owned) or cleared (not owned). Semaphores are a high performance IPC mechanism because they are always resident in memory and because the OS/2 routines that manipulate them are extremely fast. Figure 13-1 provides a summary of the OS/2 semaphore-related API. Function Description Access to System Semaphores DosCloseSem Closes system semaphore DosCreateSem Creates system semaphore DosOpenSem Opens existing system semaphore Semaphore Functions for Mutual Exclusion DosSemClear Releases ownership of semaphore DosSemRequest Obtains ownership of semaphore Semaphore Functions for Signaling DosMuxSemWait Waits for any of several semaphores to be clear DosSemClear Unconditionally clears semaphore DosSemSet Unconditionally sets semaphore DosSemSetWait Unconditionally sets semaphore and waits until it is clear DosSemWait Waits until semaphore is clear Fast-Safe RAM Semaphore Functions DosFSRamSemClear Releases fast-safe RAM semaphore DosFSRamSemRequest Obtains ownership of fast-safe RAM semaphore Figure 13-1. Semaphore-related API at a glance. Semaphores can be used to coordinate use of a nonreentrant resource or for signaling between threads or processes. The classic use of semaphores, which OS/2 fully supports, is to control access to a serially reusable resource (SRR). An SRR is a procedure, a device, or a data object that will be damaged or will produce unpredictable results if it is used by more than one thread or process at a time. Arranging mutual exclusion among cooperating processes is easy with semaphores. A semaphore is established that represents the resource, and a thread or process refrains from accessing the resource unless it "owns" the corresponding semaphore. For example, suppose that a process has opened an indexed database and that several threads within the process want to read or write the database using the same file handle. Each access to the file requires at least two distinct API requestsa DosChgFilePtr followed by a DosRead or DosWrite so each thread must prevent its sequence of API calls from being jumbled with similar sequences by other threads. An easy way for a thread to protect its file operations is to make them a critical section of code: DosEnterCritSec  DosChgFilePtr  DosRead or DosWrite  DosExitCritSec But this cavalier approach shuts down all other threads in the same process during the file operation, whether they are trying to use the file or not. It specifically throws away a major benefit of multitasking: the ability to continue execution during an I/O operation. A far better approach is to set up a semaphore and then abide by the convention that only the thread that owns the semaphore can use the file handle: Acquire semaphore  DosChgFilePtr  DosRead or DosWrite  Release semaphore When you use this method, other threads that do not need simultaneous access to the file can execute unhindered. OS/2 semaphores can also be used for synchronization or for signaling between threads or processes. In this usage, one or more threads can suspend themselves by calling an API function to block on a semaphore. When the semaphore is cleared, threads that were blocking on that semaphore will wake up and run (subject to their priority, the type of wait operation, the semaphore's not being set again before a particular thread receives a timeslice, and so forth). When a semaphore is used for signaling and no resource that can be corrupted is involved, any thread that knows about the semaphore can set, clear, or test it at any time. Using semaphores for signaling is appropriate when only one thread is in a position to detect an event but other threads may want to take action based on this event. For example, a screen dump utility might decouple its keyboard monitoring from the unpredictable delays involved in a screen pop-up or disk write by establishing a separate thread for each of these tasks. When the keyboard thread detects the hot key, it can simply clear a semaphore to release the screen and disk I/O threads to do their work (see SNAP.ASM in Chapter 18). Coded in this manner, the process never compromises the ability of the keyboard thread to respond rapidly to keystrokes. OS/2 Semaphore Types To provide for the somewhat different requirements of interthread communication, interprocess communication, and dynlink libraries, OS/2 supports three types of semaphores: RAM semaphores, system semaphores, and fast-safe RAM semaphores. RAM semaphores let you send signals or control resources among multiple threads in the same process or between multiple processes that share memory. Each RAM semaphore requires the allocation of a doubleword of storage, which should be initialized to zero before the semaphore is used. The handle for a RAM semaphore is simply its 32-bit address (selector and offset); consequently, the number of RAM semaphores that a process can manipulate is limited only by the amount of memory it can allocate. System semaphores are named objects that you can use for signaling or synchronization between processes; the name always takes the form: \SEM\path\name.ext where the path and the extension (.ext) are optional. OS/2 allocates the storage for a system semaphore outside the creating process's memory space, and the pool of available system semaphores is relatively small (128 in OS/2 version 1.0 and 256 in version 1.1). A system semaphore is created with the function DosCreateSem, whose parameters are the address of an ASCIIZ semaphore name, the address of a doubleword variable that receives a semaphore handle, and a flag that indicates whether ownership of the semaphore is exclusive or nonexclusive. (Only the process that owns an exclusive semaphore can change its state, although other processes can open the semaphore and test its state or block on it.) If the create operation is successful, the system returns a semaphore handle; the initial state of the semaphore is "not owned" (clear). To gain access to an existing system semaphore, call DosOpenSem. This function requires the addresses of an ASCIIZ semaphore name and a doubleword of storage to receive a semaphore handle. Opening a semaphore does not establish ownership of, test, or change the value of the semaphore. Figure 13-2 illustrates the procedure for creating or opening a system semaphore. ; error code sname db '\SEM\MYSEM',0 ; semaphore name shandle dd 0 ; receives semaphore handle . . . ; create semaphore... push 1 ; 1 = not exclusive push ds ; receives semaphore handle push offset DGROUP:shandle push ds ; address of semaphore name push offset DGROUP:sname call DosCreateSem ; transfer to OS/2 or ax,ax ; semaphore created? jz label1 ; yes, proceed ; no, does it exist? cmp ax,ERROR_ALREADY_EXISTS jne error ; jump if other error ; semaphore exists, open it... push ds ; receives semaphore handle push offset DGROUP:shandle push ds ; address of semaphore name push offset DGROUP:sname call DosOpenSem ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed label1: ; semaphore is now created ; or opened... . . . Figure 13-2. Creating or opening a system semaphore. After you obtain a handle to a system semaphore, you can use it with the functions to test, set, and wait on semaphores exactly as you would use a RAM semaphore handle (which is an ordinary selector and offset). Knowing this, you might correctly predict that system semaphore handles, unlike file and pipe handles, cannot be inherited by child processes. A system semaphore handle is not, however, simply a special selector and offset combination; it is a "magic" value and cannot be used to inspect the contents of the semaphore. Fast-safe RAM semaphores are designed for use by dynlink libraries with multiple client processes; they have characteristics of both RAM and system semaphores. You can use only two special-purpose functions to manipulate them, and you cannot interchange them with normal RAM and system semaphores. Mutual Exclusion with Semaphores oA thread uses DosSemRequest to establish ownership of a semaphore and, by extension, ownership of the resource associated with the semaphore. DosSemRequest is called with the handle of a system or RAM semaphore and a timeout parameter. If the semaphore is currently unowned, the function marks it as owned and returns a success code. If the semaphore is already owned, the system either suspends the calling thread indefinitely until the semaphore becomes available, waits for a specified interval and then returns (if the semaphore does not clear) with an error code, or returns immediately with an error codedepending on the timeout parameter. The function DosSemClear is called with a semaphore handle; it releases the thread's ownership of that semaphore, which implicitly releases the associated resource. If the semaphore is currently unowned, no error is returned. If another thread has been waiting for the semaphore with a blocked DosSemRequest call, its request then succeeds and the suspended thread is reawakened. When multiple threads are waiting for the semaphore and the semaphore becomes available, the DosSemRequest of the thread with the highest priority succeeds. The threads with lower priorities might sleep through many requests and releases of the semaphore until no requests are pending from higher priority threads. If all of the waiting threads have the same priority, you cannot predict which thread will acquire the semaphore when it is released by the current owner. Recursive requests for a system semaphore are supported only if the semaphore was created with the exclusive option. OS/2 maintains a use count which is incremented by DosSemRequest and decremented by DosSemClear; the semaphore is not actually released until the use count becomes zero. Recursive use of RAM semaphores is not supported. Signaling with Semaphores A semaphore is set unconditionally by calling DosSemSet with the semaphore handle (Figures 13-3 and 13-4 on the following page). Similarly, you can unconditionally clear a semaphore by calling DosSemClear with the semaphore handle. If any threads are blocked on the semaphore, they are released and will run at the next opportunity, provided that the semaphore is not set again in the meantime. (The sole exception is explained below.) OS/2 contains several functions that allow a thread to suspend itself until one or more semaphores are cleared. The prototypal function is DosSemWait, which is called with a semaphore handle and a timeout option. If the semaphore is clear, the thread regains control immediately with no error; otherwise, the thread blocks as determined by the timeout option: wait for the semaphore indefinitely, return with an error if it fails to clear within the specified interval, or return immediately with an error if the semaphore is set. DosSemWait also returns an error if the semaphore handle is invalid. The function DosSemSetWait works like DosSemWait, except that it sets the semaphore if it was not already set, and then suspends the requesting thread until the semaphore is cleared by another thread or the indicated timeout expires. An important aspect of DosSemWait and DosSemSetWait is that they are level-triggered, not edge-triggered. A thread that is blocking on a semaphore with either of these functions could miss a quick clearing and resetting of the semaphore, depending on the thread's priority and scheduled position and on the activity of other threads in the system. sname db '\SEM\MYSEM',0 ; semaphore name shandle dd 0 ; receives semaphore handle . . . ; open system semaphore... push ds ; receives semaphore handle push offset DGROUP:shandle push ds ; address of semaphore name push offset DGROUP:sname call DosOpenSem ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed . . . ; set system semaphore... push word ptr shandle+2 ; handle from DosOpenSem push word ptr shandle call DosSemSet ; transfer to OS/2 or ax,ax ; did set succeed? jnz error ; jump if function failed . . . Figure 13-3. Opening and setting a system semaphore. mysem dd 0 ; storage for RAM semaphore ; (the address of mysem is ; the semaphore handle) . . . ; set RAM semaphore... push ds ; semaphore handle = address push offset DGROUP:mysem call DosSemSet ; transfer to OS/2 or ax,ax ; did set succeed? jnz error ; jump if function failed . . . Figure 13-4. Setting a RAM semaphore. Note that a RAM semaphore should be initialized to zero before it is used for the first time. The function DosMuxSemWait is called with a list of as many as 16 semaphores and a timeout option. The calling thread is suspended until any of the indicated semaphores is cleared or the function has timed out. Unlike DosSemWait and DosSemSetWait, DosMuxSemWait is edge-triggered: The waiting thread will always be awakened (at some point) if a semaphore in the list changes state from set to clearedeven if the semaphore gets set again before the waiting thread has an opportunity to run. Closing System Semaphores To end a process's access to a system semaphore, call DosCloseSem with the semaphore handle. Any subsequent use of the semaphore handle results in a GP fault. If a process terminates with open system semaphores, the system closes them on behalf of the process. A system semaphore ceases to exist when all processes that use the semaphore have issued DosCloseSem or terminated. An interesting situation occurs when a process terminates while it owns a system semaphore that is still being used by other processes. Any threads in other processes that are waiting for the semaphore to clear or that subsequently attempt to acquire the semaphore receive an error code indicating that the owning process may have terminated abnormally. One of those threads must then clean up the resource represented by the semaphore and then release the semaphore for further use with DosSemClear. A superior way of handling this cleanup problem takes advantage of the fact that the system always runs a process's ExitList procedures before it closes the process's remaining system semaphores. Any process that uses such semaphores should therefore use DosExitList to register an ExitList procedure that releases all semaphores and restores the associated system resources to a known state. This approach puts the burden of cleanup where it belongs so that other processes, which did not cause the error, do not have to figure out how to recover from it. Using Fast-Safe RAM Semaphores Fast-safe RAM semaphores were introduced in OS/2 version 1.1 to meet the needs of dynlink libraries with multiple clients. They combine the high speed of RAM semaphores with the system semaphore capabilities of "counting" and ownership tracking. Fast-safe RAM semaphores are manipulated with the DosFSRamSemRequest and DosFSRamSemClear functions. The fast-safe RAM semaphore itself is a 14-byte structure in the memory space belonging to the process or dynlink library. The system supports nested calls to DosFSRamSemRequest by requiring that an equivalent number of calls to DosFSRamSemClear be issued by the semaphore's owner before it clears the semaphore and releases any blocked threads. What's special about fast-safe RAM semaphores? Just this: If an ExitList routine (registered with DosExitList) calls DosFSRamSemRequest for a fast-safe RAM semaphore that is owned by any thread in the same process, the DosFSRamSemRequest succeeds and the owner thread ID and reference count fields of the semaphore are forced to 1. The ExitList routine can then take any necessary measures to clean up the resource symbolized by the semaphore and call DosFSRamSemClear to release the semaphore. Fast-safe RAM semaphores are therefore ideal for representing a resource controlled by a dynlink library and accessed by multiple client processes of that library. When the dynlink library is called for the first time by a particular process, it can register an ExitList routine on behalf of that process that will clean up the resource and release the fast-safe RAM semaphore for the resource if the process terminates abnormally. Using fast-safe RAM semaphores, the dynlink library gets the advantages of a system semaphore"counting" and cross-process usabilitybut avoids the speed penalty for a system semaphore. Anonymous Pipes Anonymous pipes are so called because (unlike system semaphores, named pipes, and queues) they do not have names and are accessed only by handles. They are an IPC mechanism midway in power between semaphores and queues. Like semaphores, pipes provide relatively high performance because the information they convey is always resident in memory; like queues, pipes can be used to pass a chunk of data of any size (up to 64 KB) between processes. Physically, an anonymous pipe is simply an area of memory owned by the operating system that is used as a ring buffer, with "in" and "out" pointers that are also maintained by the system. From a process's point of view, a pipe is manipulated somewhat like a file, but it acts more like a FIFO queue and is much faster. To create an anonymous pipe, call the function DosMakePipe, supplying a maximum pipe size (as large as 64 KB less 32 bytes for a control header) and the addresses of two variables that receive read and write handles for the pipe. The size parameter is advisorythe actual size of the pipe buffer can be smaller depending on available memory; in any event, the size affects only the pipe's performance, not its behavior. DosMakePipe fails if memory is insufficient to create the pipe or if the process has exhausted its supply of handles. The two handles returned by DosMakePipe are assigned from the same sequence as those returned by DosOpen for files and character devices. Any child processes started after the pipe is created inherit the handles and have access to the pipe. A common application of pipes, illustrated below, is to redirect a child process's standard input and standard output handles to pipe handles; thereafter, the child process unknowingly communicates with the parent process for its input and output instead of with the keyboard and screen. A simplified example of this technique is shown in Figure 13-5 on the following page. Create two pipes with DosMakePipe  Redirect standard device handles to pipe handles  DosExecPgm child process  Communicate with child process via pipes  DosCwait to synchronize with child's termination  Restore standard device handles to previous meanings  DosClose pipe handles to destroy pipes Processes that are not direct descendants of an anonymous pipe's creator cannot inherit the handles for the pipe and thus have no way to access it. The two major restrictions on the use of anonymous pipes are thus their limitation to use for communication between closely related processes, and the 64 KB limit on the amount of data that a pipe can contain "in transit." To read from or write to a pipe synchronously, call DosRead or DosWrite with the appropriate pipe handle, buffer address, and record length. You can read and write a pipe asynchronously with DosReadAsync and DosWriteAsync, which return control to the requesting thread immediately and clear a semaphore when the operation is complete. Because anonymous pipes must be used within a closely related group of processes, they have no associated permission mechanisms. Don't be misled by the superficial similarities between pipe handles and file handlesthey behave in subtly different ways. If a synchronous write is directed to a file and the disk is full, DosWrite does not return an errorit simply returns a count of bytes transferred that is smaller than the length requested. But if a synchronous write is performed to a pipe handle and the pipe is full, the requesting thread is blocked until another thread removes enough data from the pipe so that the write can be completed. Similarly, if a synchronous read is performed with a file handle and the file pointer is at or near the end of file, DosRead returns anyway, indicating that a partial record or no bytes were transferred. But if DosRead is called with a pipe handle and the pipe is empty, the thread is suspended until some other thread writes enough data into the pipe to satisfy the request. This un-filelike behavior can change when pipe handles are closed by participating processes. If all read handles to a pipe are closed, a DosWrite to the pipe returns an error. If all write handles to a pipe are closed, a DosRead of the pipe returns an error. stdin equ 0 ; standard input handle preadh dw ? ; handle to read pipe pwriteh dw ? ; handle to write pipe stdinh dw stdin ; stdin handle to redirect wlen dw ? ; receives bytes written msg db 'Hello kid!' ; message for child process msg_len equ $-msg . . . ; first create pipe... push ds ; receives read handle push offset DGROUP:preadh push ds ; receives write handle push offset DGROUP:pwriteh push 0 ; max pipe size = default call DosMakePipe ; transfer to OS/2 or ax,ax ; was pipe created? jnz error ; jump if function failed ; redirect standard input ; to read from pipe... push preadh ; pipe read handle push ds ; standard input handle push offset DGROUP:stdinh call DosDupHandle ; transfer to OS/2 or ax,ax ; redirection successful? jnz error ; jump if function failed . ; start child process . ; with DosExecPgm here... . ; (code not shown) ; send message to child ; through pipe... push pwriteh ; pipe write handle push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; did write succeed? jnz error ; jump if function failed . . . Figure 13-5. Simplified example of IPC using anonymous pipes. A pipe is created, the standard input handle is redirected to the pipe's read handle, and a child process is started. A message written into the pipe by the parent is received by the child when it issues a DosRead to its inherited standard input handle. Named Pipes Named pipes were originally designed for use in the OS/2 LAN Manager, but as their general usefulness became apparent, they were subsumed into the OS/2 kernel with version 1.1. Named pipes can be used for communication between unrelated processes running on the same system or between processes running on different machines on a local area network; they always have names of the following form: \PIPE\path\name.ext where the path and the extension (.ext) are optional. Figure 13-6 summarizes the OS/2 API for both anonymous and named pipes. Function Description Anonymous Pipes DosMakePipe Creates anonymous pipe, returns read and write handles Named Pipes DosCallNmPipe Opens, writes, reads, then closes named pipe (C) DosConnectNmPipe Waits for client to open pipe (S) DosDisConnectNmPipe Unilaterally closes named pipe (S) DosMakeNmPipe Creates named pipe and returns handle (S) DosPeekNmPipe Looks ahead for data in named pipe (B) DosQNmPHandState Returns state information for named pipe (B) DosQNmPipeInfo Returns information about named pipe (B) DosQNmPipeSemState Returns information for pipe associated with semaphore (B) DosSetNmPHandState Sets state information for named pipe (B) DosSetNmPipeSem Associates a semaphore with a named pipe (B) DosTransactNmPipe Writes, then reads, named pipe (B) DosWaitNmPipe Conditionally waits to open named pipe (C) Figure 13-6. Pipe-related IPC functions at a glance. S = usable by named pipe servers (creators) only; C = usable by named pipe clients only; B = usable both by named pipe servers and by clients. A typical application will use only a few of the functions that the API provides for manipulating named pipes. The pipe's creator, or server, calls DosMakeNmPipe to create a named pipe. The parameters for this function include various pipe characteristics, the maximum number of instances of the pipe that can exist, and advisory sizes for the pipe's input and output buffers. The server process can use DosConnectNmPipe to wait until another process has opened a pipe by name; DosRead, DosReadAsync, DosWrite, or DosWriteAsync to obtain data from or put data into the pipe; and DosPeekNmPipe to inspect data in the pipe without removing it. A server can also unilaterally break a pipe connection with another process with DosDisConnectNmPipe. A client process, that is, a process which did not create the pipe but wants to use it, can obtain a handle for the pipe with DosOpen and then access the pipe with DosRead, DosReadAsync, DosWrite, and DosWriteAsync. A client process can also take advantage of the special functions DosCallNmPipe or DosTransactNmPipe to exchange data with the pipe's creator in one operation. A client releases a handle for a named pipe explicitly with DosClose or implicitly at process termination. Both server and client processes can use the functions DosDupHandle, DosQHandType, DosQFHandState, DosSetFHandState, and DosBufReset with a handle for a named pipe as though it were a file handle. Among other things, this similarity allows a child process that inherits handles for a named pipe to determine its nature. When all the client handles for a named pipe have been closed, the pipe cannot be reopened until the server issues DosDisConnectNmPipe followed by DosConnectNmPipe. When all client and server handles for a named pipe have been closed, the pipe is destroyed. Figure 13-7 depicts the various possible states for a named pipe. Server DosMakeNmPipe Ŀ Pipe Server DosClose  disconnected Ŀ Server DosConnectNmPipe Ŀ Pipe Server DosClose listening  Client DosOpen Server Ŀ DosDisConnectNmPipe Pipe Ĵ connected Client or Server DosClose  /\ / \ / \ / All \ / handles \ Yes Ŀ \ closed? / Pipe \ / destroyed Client \ / or \ / Server \/ DosClose No Ŀ Ĵ Pipe closing Server DosDisConnectNmPipe Figure 13-7. State transitions for a named pipe. An important feature of named pipesas compared to anonymous pipesis the fact that you can create them as either byte stream pipes or message pipes. A byte stream pipe works like an anonymous pipe and is subject to the same special considerations for DosRead and DosWrite. A message pipe behaves somewhat like a FIFO queue; the length of each message written into the pipe is encoded in the pipe, and DosRead returns at most one message at a time regardless of the number of bytes requested. If you call DosRead with a buffer that is too small for the message, it returns a partial message along with an error code; you can recover the remainder of the message with additional calls to DosRead. Shared Memory Shared memory segments are potentially the fastest IPC mechanism. If two or more processes have a selector to the same segment, they can pass data back and forth at speeds limited only by the CPU's ability to copy bytes from one place to anotherthe mechanism requires no additional calls to the operating system. The threads and processes sharing the segment are responsible for using semaphores or other interlocks to synchronize any changes in its content. OS/2 supports two distinct methods by which processes can share memory: creation of named segments, and giving and getting of selectors. Each method offers specific advantages for data protection and speed of access. A named shareable segment is created by calling DosAllocShrSeg with the segment size, the address of a variable to receive a selector, and the address of an ASCIIZ segment name in the following form: \SHAREMEM\path\name.ext where the path and the extension (.ext) are optional (example in Figure 13-8). The segment is always movable and swappable. DosAllocShrSeg fails if the system's virtual memory (physical memory plus swap space) is exhausted, if all shareable selectors are in use, or if a segment by the same name already exists. After you create a segment with DosAllocShrSeg, any process that knows the segment's name can obtain a valid selector for it with DosGetShrSeg. The selector for a particular named segment is identical across all processes, although a descriptor is not actually built in a process's LDT until the process invokes DosGetShrSeg. ; name of shared segment segname db '\SHAREMEM\MYSEG',0 sel dw ? ; receives selector . . . ; create named shareable ; segment... push 32768 ; segment size push ds ; address of name push offset DGROUP:segname push ds ; receives selector push offset DGROUP:sel call DosAllocShrSeg ; transfer to OS/2 or ax,ax ; segment created? jnz error ; jump if function failed . . . Figure 13-8. Creating a 32 KB named shared memory segment. Sharing a memory segment by getting and giving selectors is somewhat more complicated because the selectors must be passed between the processes by another means of IPC (such as a pipe, a queue, or a named segment). To allocate a givable or gettable shared segment, call DosAllocSeg or DosAllocHuge in the usual manner, with the proper bits in the function's shareable/discardable parameter set to indicate whether new selectors and descriptors will be needed later and how they will be obtained. When a process gives away a selector to another process, it calls DosGiveSeg with its own selector for the segment and the PID of the receiving process. DosGiveSeg builds a descriptor in the receiving process's LDT that maps to the same physical memory. It returns the corresponding selector to the calling process, which must then communicate the new selector to the receiving process by some IPC method. Segment giving is best suited to IPC between closely related processes because the receiving process's PID is usually known only to its parent. Segment giving is demonstrated as part of the queue write example in Figure 13-10 on p. 290. When a gettable segment is allocated, on the other hand, the memory manager reserves the corresponding selector position in the LDT of every process in the system. Initially, however, a descriptor is built only in the allocating process's LDT. Any other process can, once it knows the selector value, make that selector valid for its own access by calling DosGetSeg to build a corresponding LDT descriptor. Segment getting allows far pointers to be passed around freely, without the need for one process to know another's PID, but it is a correspondingly less secure technique than the use of givable selectors. A process can give up its right of access to a named, gettable, or givable shared memory segment by calling DosFreeSeg with the segment's selector. Subsequent use of that selector by the process results in a GP fault. OS/2 maintains a reference count for each shared segment. When all processes using a segment have released their selectors for the segment or have terminated, OS/2 destroys the segment and returns its memory to the global pool. Queues Queues are the most powerful IPC mechanisms in OS/2 and the most complex to use. Queues are slower than pipes (and much slower than communication by semaphores or shared memory segments), but they are also far more flexible, as suggested by the following list of characteristics: Queues are named objects which any process can open and write. The amount of data in a queue is limited only by available virtual memory. Each record in a queue is a separately allocated block of memory storage and can be as large as 64 KB. The records in a queue can be ordered by first-in-first-out (FIFO), last-in-first-out (LIFO), or priority. The queue owner can examine records in the queue and remove them selectively, in any order. Data in the queue is not copied from place to place by the operating system; instead, pointers are passed to shared memory segments. In essence, an OS/2 queue is an ordered list of shared memory segments; the operating system maintains and searches the list on behalf of the communicating processes. Figure 13-9 summarizes the queue-related functions in the OS/2 API. To create a queue, call DosCreateQueue with an ASCIIZ queue name, a parameter that specifies ordering method for the queue (FIFO, LIFO, or priority), and the address of a variable to receive a queue handle. The name of a queue always takes the following form: \QUEUES\path\name.ext where the path and the extension (.ext) are optional. An error is returned if a queue with the same name already exists, if the name or queue ordering is invalid, or if there is not enough free memory to establish the queue's supporting data structure. The creator of a queue is called the queue owner. After a queue is created, any process can open it by calling DosOpenQueue with an ASCIIZ queue name and pointers to two variables that receive a queue handle and the PID of the queue owner. Function Description Queue Access DosCloseQueue Closes queue, destroys queue if owner (A) DosCreateQueue Creates a queue and returns handle (O) DosOpenQueue Opens existing queue and returns handle (A) Queue Reads and Writes DosPeekQueue Nondestructively reads queue message (O) DosReadQueue Reads message from queue (O) DosWriteQueue Writes message into queue (A) Queue Information DosPurgeQueue Discards all messages in queue (O) DosQueryQueue Returns number of messages in queue (A) Figure 13-9. OS/2 queue management API at a glance. O = usable only by queue owner; A = usable by any process. Any thread in any process that has a queue handle can write to the queue, but adding records to a queue is much more involved than writing a record to a pipe. The procedure can be summarized as follows: DosOpenQueue  DosAllocSeg with givable flag Ŀ  Copy message to segment  DosGiveSeg to create selector for queue reader  DosWriteQueue  DosFreeSeg of original selector from DosAllocSeg  DosCloseQueue In this sequence, the writer allocates a memory segment of appropriate size with DosAllocSeg, specifying that the segment is givable, and copies the data for the queue record into it. Then the writer uses DosGiveSeg, together with the PID returned by DosOpenQueue, to obtain a giveaway selector that can be passed to the queue owner. Next, the writer calls DosWriteQueue with a queue handle, a priority, the selector (obtained from DosGiveSeg) and offset for the queue message, and the message length. DosWriteQueue fails if the queue handle is invalid or if system memory is insufficient to expand the supporting structure of the queue; obviously, the whole sequence can also fail at an earlier point (in either DosAllocSeg or DosGiveSeg) if the system runs out of virtual memory or selectors. Last, if the writer is using segments as individual queue messages (the usual case), it should release its original selector for the shareable segment with DosFreeSeg. Figure 13-10 demonstrates the writing of a message to a queue. qhandle dw ? ; receives queue handle qowner dw ? ; receives queue owner PID qname db '\QUEUES\MYQ',0 ; queue name qselw dw ? ; queue writer's selector qselr dw ? ; queue reader's selector qmsg db 'This is my queue message' qmsg_len equ $-qmsg . . . ; first open the queue... push ds ; receives owner's PID push offset DGROUP:qowner push ds ; receives queue handle push offset DGROUP:qhandle push ds ; address of queue name push offset DGROUP:qname call DosOpenQueue ; transfer to OS/2 or ax,ax ; did open succeed? jnz error ; jump if function failed ; allocate givable segment push qmsg_len ; length of segment push ds ; receives segment selector push offset DGROUP:qselw push 1 ; 1 = segment givable call DosAllocSeg ; transfer to OS/2 or ax,ax ; did allocation succeed? jnz error ; jump if function failed mov es,qselw ; copy queue message to xor di,di ; giveaway segment mov si,offset DGROUP:qmsg mov cx,qmsg_len cld rep movsb push ds ; restore ES = DGROUP pop es ; get giveaway selector... push qselw ; original selector push qowner ; PID of queue reader push ds ; receives new selector push offset DGROUP:qselr call DosGiveSeg ; transfer to OS/2 or ax,ax ; new selector obtained? jnz error ; no, can't send message ; write message to queue... push qhandle ; handle from DosOpenQueue push 0 ; private data push qmsg_len ; queue message length push qselr ; queue message address push 0 ; (using givable selector) push 0 ; queue element priority call DosWriteQueue ; transfer to OS/2 or ax,ax ; did queue write succeed? jnz error ; jump if function failed ; release original selector ; for shared segment... push qselw ; original selector call DosFreeSeg ; transfer to OS/2 or ax,ax ; did release succeed? jnz error ; jump if function failed . . . ; now close the queue... push qhandle ; handle from DosOpenQueue call DosCloseQueue ; transfer to OS/2 or ax,ax ; did close succeed? jnz error ; jump if function failed . . . Figure 13-10. Opening an existing queue, writing a message into it, and then closing it. Only the queue owner can inspect or remove messages in a queue. The procedure can be summarized as follows: DosCreateQueue  DosReadQueueĿ  DosFreeSeg  DosCloseQueue The principal queue-reading function, DosReadQueue, is called with a queue handle and several other selection parameters; it returns a pointer and length for a queue element. The owner can choose a message from the queue based on its position or priority or simply take the next message in line. Reading a queue can be a synchronous operation (with the calling thread suspended until a record is available) or an asynchronous operation (with the calling thread regaining control immediately and a semaphore cleared when a message becomes available). If messages are being passed one per segment, the queue reader should release the selector as soon as it has processed the message so that the amount of virtual memory tied up in the queue will not grow out of control. Figure 13-11 offers an example of reading a message from a queue. qhandle dw ? ; receives queue handle qname db '\QUEUES\MYQ',0 ; queue name qident dw 0,0 ; writer's PID, event code qmsglen dw ? ; length of queue message qmsgptr dd 0 ; address of queue message qmsgpri dw ? ; priority of queue message . . . ; first create queue... push ds ; receives queue handle push offset DGROUP:qhandle push 0 ; 0 = FIFO queue ordering push ds ; address of queue name push offset DGROUP:qname call DosCreateQueue ; transfer to OS/2 or ax,ax ; was create successful? jnz error ; jump if function failed ; read message from queue... push qhandle ; queue handle push ds ; receives PID, event code push offset DGROUP:qident push ds ; receives message length push offset DGROUP:qmsglen push ds ; receives message pointer push offset DGROUP:qmsgptr push 0 ; 0 = read first element push 0 ; 0 = synchronous read push ds ; receives message priority push offset DGROUP:qmsgpri push 0 ; semaphore handle (unused push 0 ; by synchronous read) call DosReadQueue ; transfer to OS/2 or ax,ax ; did read succeed? jnz error ; jump if function failed les bx,qmsgptr ; now let ES:BX point ; to queue message . ; process the queue . ; message here... . ; release selector for ; queue message... push ds ; restore ES = DGROUP pop es push word ptr qmsgptr+2 ; selector for message call DosFreeSeg ; transfer to OS/2 or ax,ax ; did release succeed? jnz error ; jump if function failed ; close the queue, ; also destroying it... push qhandle ; handle from DosCreateQueue call DosCloseQueue ; transfer to OS/2 or ax,ax ; did close succeed? jnz error ; jump if function failed . . . Figure 13-11. Creating a queue, waiting for a message to be written into it, retrieving the message, and then closing the queue (also destroying it because the owner is closing it). DosPeekQueue is similar to DosReadQueue, but the record is not removed from the queue when it is retrieved. Thus, the queue owner can scan the data waiting in the queue and decide on a processing strategy without disturbing the ordering of the records or copying the records to its own memory for inspection. Other, less frequently used queue functions include DosQueryQueue, which allows any process that can supply the handle to a queue to obtain the number of elements currently in the queue, and DosPurgeQueue, which discards all records in a queue. DosPurgeQueue can be invoked only by the queue owner. A process relinquishes its right of access to a queue by calling DosCloseQueue with the queue handle. When the queue owner closes the queue or terminates, the queue is purged, its supporting data structure is destroyed, and any further attempts by other processes to write to the queue return an error. However, the memory segments that contained the queue messages are not destroyed until all processes with valid selectors for the segments release them with DosFreeSeg or terminate. Signals Signals are a unique IPC mechanism: They essentially simulate a hardware interrupt of the receiving process. If a signal occurs and the process has previously indicated its ability to handle that signal type, control transfers immediately to the routine designated as the signal handler. After the handler completes its processing and returns, control returns to the point of interruption. OS/2 has two basic classes of signals. The first class of signals includes those listed in Figure 13-12. The SIGTERM signal results from a call to DosKillProcess by another process; it terminates the receiving process if that process has not registered a handler for it. SIGINTR and SIGBREAK signals go to the last process in a process subtree to register a handler for them. If an application doesn't register its own handler for SIGINTR and SIGBREAK, they are ordinarily fielded by the ancestor command process, which translates them to a SIGTERM signal for all its descendants. A process ignores SIGBROKENPIPE unless a handler has been registered for it. Signals in the second class are known as event flags, and three are available: Flags A, B, and C. A process can send an event flag only to itself or its descendants, and it can send a single word of private data along with the flag. The meaning of the private data is known only to the signaler and signalee; it is ignored by OS/2. If an event flag signal is directed to a process which has not registered a handler for it, the signal is discarded. A process registers a signal handler by calling DosSetSigHandler with the address of the handler, a code selecting one of the seven signals just described, and a code indicating the action to be taken when the signal occurs: ignore the signal, give control to the system's default handler, give control to the process's own handler, reset the current signal without affecting its disposition, or return an error to the process that sent the signal. To register handlers for more than one signal type, a process must make an individual DosSetSigHandler call for each. Signal Description SIGINTR Ctrl-C was detected SIGBREAK Ctrl-Break was detected SIGTERM Process is being terminated SIGBROKENPIPE Pipe read or write failed Figure 13-12. OS/2 signals in the first class. A process calls DosFlagProcess to send an event flag signal (A, B, or C) and an arbitrary word of data to itself or to a descendant process, and optionally to the entire subtree of the receiving process. An error is returned if the receiving process has previously registered its refusal to accept the incoming signal type (with a call to DosSetSigHandler). If no error is returned, the destination process either accepted the signal or has not registered a handler for it. On the receiving end, when a signal occurs for which a handler has been registered, the process's primary thread (the thread which received control at the original entry point) is interrupted and control is transferred with a forced far call to the signal handler. If the primary thread is in the middle of an OS/2 call that will not complete quickly (primarily character device I/O calls), that function is aborted and returns with an error. Otherwise, the signal occurs immediately upon the thread's return from the executing function. Because the primary thread can thus be interrupted at any time for an unknown interval, an application that expects to receive signals should reserve the primary thread for that purpose. When the process is started, it can register its signal handlers, create a new thread that becomes the main line of execution for the process, and then block the primary thread on a semaphore. When a signal handler receives control, OS/2 has set up the stack (which is the primary thread's usual stack) with the following contents: SS:SP Far return address for handler SS:SP+4 Signal number being serviced 1 = Ctrl-C 2 = broken pipe 3 = process termination 4 = Ctrl-break 5 = Flag A 6 = Flag B 7 = Flag C SS:SP+6 Private data passed by signaling process if signal is Flag A, B, or C All registers other than SS, SP, IP, CS, and the CPU flags contain the values they held at the point of the signal's arrival. During signal processing, the handler must call DosSetSigHandler again with action code 4 (reset current signal); if it does not do so, further signals of that type will not be serviced. The handler exits with a far return, clearing the stack of the additional two words containing the signal number and the signal argument. OS/2 then restores all register contents and returns control to the point of interruption. Alternatively, the handler can reset the stack frame to some desired state and branch directly to another point in the program; that is, no harm is done if the handler never returns. Skeleton code for MASM and C signal handlers appears in Figures 13-13 and 13-14. You can suppress signal handling temporarily with DosHoldSignal so that critical sections of code or "slow" OS/2 calls are not interrupted. The signals that occur while a DosHoldSignal is in effect are postponed and not lost. Naturally, you should postpone signals for as short a period as possible treat them as though they were hardware interrupts being disabled. SIGINTR equ 1 ; Ctrl-C signal number prvact dw ? ; receives previous action prvhdlr dd ? ; receives address of ; previous signal handler dummy dd ? ; dummy storage for type 4 ; DosSetSigHandler call . . . ; register signal handler... push cs ; address of new handler push offset _TEXT:handler push ds ; receives address of ; previous handler push offset DGROUP:prvhdlr push ds ; receives previous action push offset DGROUP:prvact push 2 ; action = 2: call handler push SIGINTR ; signal of interest = Ctrl-C call DosSetSigHandler ; transfer to OS/2 or ax,ax ; was handler registered? jnz error ; jump if function failed . . . handler proc far ; handler for Ctrl-C signal . ; application-specific . ; signal processing here... . ; reset signal so another ; can be processed... push cs ; address of handler push offset _TEXT:handler push ds ; previous handler push offset DGROUP:dummy push ds ; previous action push offset DGROUP:dummy push 4 ; action 4 = reset signal push SIGINTR ; signal of interest = Ctrl-C call DosSetSigHandler ; transfer to OS/2 ret 4 ; return from handler ; and clear stack handler endp Figure 13-13. Registration of a MASM handler for Ctrl-C signals, along with a skeleton for the handler itself. #define SIGINTR 1 /* Ctrl-C signal number */ /* function prototypes */ void far pascal handler(unsigned, int); unsigned extern far pascal DosSetSigHandler(void (pascal far *) (unsigned, int) unsigned long far *, unsigned far *, int, int); unsigned extern far pascal DosSleep(unsigned long); main() { unsigned long prevh; /* prev. handler address */ unsigned preva; /* prev. handler action */ . . . /* register handler */ DosSetSigHandler(handler, &prevh, &preva, 2, SIGINTR); . . . } /* This is the actual signal handler for Ctrl-C. */ void far pascal handler(unsigned sigarg, int signum) { unsigned long dummy1; /* scratch variables */ unsigned dummy2; /* reset signal */ DosSetSigHandler(handler, &dummy1, &dummy2, 4, SIGINTR); /* announce signal */ fprintf(stderr,"\nCtrl-C Signal Received!\n"); } Figure 13-14. Registration of a C handler for Ctrl-C signals, along with a skeleton for the handler itself. Chapter 14 IOPL Segments As a single-user multitasking operating system, OS/2 adheres to a philosophy of protection that is vastly different than that of its multi-user cousins. Because users assume responsibility both for the programs they install on their systems and (if necessary) for securing physical access to the system, OS/2 allows applications a degree of freedom that would be unthinkable in a multi-user environment such as UNIX/XENIX. For example, programs can generate machine code dynamically in a data segment and then execute it with the aid of DosCreateCSAlias, and they can filter the raw data stream of the keyboard, mouse, and printer drivers with the device monitor API. They can even read or write I/O ports without relying on a device driver. The ability to bypass OS/2 and access the I/O ports of an adapter directly is particularly important for developers of graphics applications that are not written to run in a Presentation Manager window or that rely on video modes or capabilities that are not supported by OS/2's built-in display functions. OS/2 places only three constraints on such programs: They may not service interrupts. (Control of the interrupt system is reserved for the kernel and true device drivers.) The hardware-dependent code must be segregated into special segments called IOPL segments. The code running in an IOPL (I/O Privilege Level) segment can call only a restricted set of API functions. An application that manipulates the video adapter directly must also, of course, cooperate with OS/2 to save and restore the screen and adapter state across session switches. Dynlink libraries can contain IOPL segments, which execute on behalf of the calling process. A dynlink library can therefore act much like a device driver from an application's perspective, concealing hardware characteristics inside a set of routines with a formalized parameter-passing scheme and named entry points. The OS/2 video subsystem, for example, is implemented primarily as dynlink libraries. What Is IOPL? Whereas segment selectors and their associated descriptors govern memory addressability for a protected mode process, as discussed in Chapter 11, the behavior of the process is also constrained by the 80286's support for four privilege levelsrings 0, 1, 2, and 3. Programs running at ring 0 have unrestricted access to the hardware, including the ability to execute any instruction, to read or write any I/O port, and to manipulate the special registers and tables that control memory protection and virtual memory. Rings 1 through 3 are intended for the execution of progressively "less trusted" programs. Transitions from one ring to another are strictly controlled by means of call gates, which can be set up only by a program that is running at ring 0. Programs in ring 1, 2, or 3 cannot execute certain instructions that would compromise memory protection, nor can they touch memory segments for which they have inadequate access rights. (Included in the descriptor for each memory segment is an indication of the privilege level required for using that segment's selector.) Nevertheless, programs in ring 1, 2, or 3 can gain restricted access to the hardware depending on the value of the IOPL field in the CPU flags register. Whenever a program is running in a ring whose number is less than or equal to the contents of the IOPL field, it can use the six instructions IN, OUT, INS, OUTS, STI, and CLI without causing a protection fault. Needless to say, the IOPL field itself can be modified only by a program running at ring 0. Under OS/2, ring 0 is reserved for the operating system kernel and its device drivers. Ring 1 is not used at all, and normal application code runs at ring 3. After system initialization, OS/2 sets the IOPL level to 2 and uses ring 2 only for the execution of code within application IOPL segments (Figure 14-1). A program must declare at link time the names of its IOPL segments, along with the names of the routines within them which will be accessible to the rest of the application. Ŀ Figure 14-1 can be found on page 303 of the printed manual. Figure 14-1. Use of the four 80286 privilege levels under OS/2. When an application containing an IOPL segment is loaded, OS/2 sets up call gates for each "exported" routine in that segment, and it resolves the references to those routines throughout the program so that they point to the call gate. When the program's mainline code, which is running at ring 3, calls a routine in an IOPL segment, the hardware traps the reference to the call gate. The CPU then changes the privilege level to ring 2, switches to a new stack, copies parameters from the old stack to the new one, and enters the IOPL routine. When the IOPL routine exits with a far return, the system restores the original privilege level and stack, clears the parameters from the caller's stack, and continues execution. Application programs with IOPL, like MS-DOS programs running in DOS compatibility mode, are clearly a weak point in system security. One can easily imagine, for example, an OS/2 protected mode Trojan horse program that would masquerade as a public domain utility but would use an IOPL segment to take over the disk controller and destroy the disk directories and file allocation table. To prevent such disasters, OS/2 can be configured so that it will not allow execution of MS-DOS programs or protected mode programs requiring IOPL. If the CONFIG.SYS file contains the statement PROTECTONLY=YES, MS-DOS applications are not supported; if the statement IOPL=NO is present, applications with IOPL segments are not loaded. API Functions and IOPL In OS/2 version 1.0, code running in an IOPL segment cannot call kernel API functions; all API calls must be made from ring 3 code. This restriction is loosened somewhat in OS/2 version 1.1, which permits code executing at ring 2 to call a limited number of API functions directly. These are known as "conforming" functions because they have a special call gate that lets them take on the privilege level of the caller. The conforming functions are listed in Figure 14-2 and are marked with an [IOPL] icon in the reference section. Conforming Functions under OS/2 Version 1.1 DosAllocHuge DosGetHugeShift DosRead DosAllocSeg DosGetInfoSeg DosReadAsync DosAllocShrSeg DosGetMachineMode DosReallocHuge DosBeep DosGetModHandle DosReallocSeg DosBufReset DosGetModName DosResumeThread DosCallback DosGetPID DosRmDir DosChDir DosGetPPID DosScanEnv DosChgFilePtr DosGetProcAddr DosSearchPath DosCLIAccess DosGetPrty DosSelectDisk DosClose DosGetResource DosSemClear DosCloseSem DosGetSeg DosSemRequest DosCreateCSAlias DosGetShrSeg DosSemSet DosCreateSem DosGetVersion DosSemSetWait DosCreateThread DosGiveSeg DosSemWait DosCwait DosHoldSignal DosSendSignal DosDelete DosKillProcess DosSetCp DosDevConfig DosLoadModule DosSetDateTime DosDevIOCtl DosLockSeg DosSetFHandState DosDupHandle DosMakePipe DosSetFileInfo DosEnterCritSec DosMemAvail DosSetFileMode DosErrClass DosMkDir DosSetFSInfo DosError DosMove DosSetMaxFH DosExecPgm DosMuxSemWait DosSetPrty DosExit DosNewSize DosSetSigHandler DosExitCritSec DosOpen DosSetVec DosExitList DosOpenSem DosSetVerify DosFileLocks DosPhysicalDisk DosSizeSeg DosFindClose DosPortAccess DosSleep DosFindFirst DosPTrace DosSubAlloc DosFindNext DosQAppType DosSubFree DosFlagProcess DosQCurDir DosSubSet DosFreeModule DosQCurDisk DosSuspendThread DosFreeSeg DosQFHandState DosTimerAsync DosFSRamSemClear DosQFileInfo DosTimerStart DosFSRamSemRequest DosQFileMode DosTimerStop DosGetCp DosQFSInfo DosUnlockSeg DosGetDateTime DosQHandType DosWrite DosGetEnv DosQVerify DosWriteAsync Figure 14-2. Kernel API functions that can be called by code executing within an IOPL segment. Four API functions are present specifically for the needs of IOPL applications: DosCLIAccess, DosPortAccess, DosR2StackRealloc, and DosCallback. DosCLIAccess informs the operating system that the application needs to execute the CLI and STI instructions to disable and enable interrupts, or that it no longer needs to use those instructions. Similarly, DosPortAccess notifies the operating system that the application requires (or no longer requires) access to a range of I/O ports. A call to DosPortAccess also implicitly requests the use of CLI and STI, so an additional call to DosCLIAccess is not required. In the current versions of OS/2, neither DosPortAccess nor DosCLIAccess does anything; an application with I/O privilege can read or write I/O ports and execute CLI or STI whether it calls these functions or not. DosPortAccess and DosCLIAccess are present for upward compatibility with future, 80386-specific versions of the operating system. The 80386 allows access to individual I/O ports to be controlled on a per-process basis by means of an I/O permissions bitmap associated with each task state segment (TSS). DosCallback and DosR2StackRealloc became available in OS/2 version 1.1. DosR2StackRealloc allows a thread to increase (but not to decrease) the size of its ring 2 stack, that is, the stack that it uses while executing within an IOPL segment. The default size of the stack allocated by OS/2 is 512 bytes, which is not large enough for routines that are heavily recursive or that call the kernel API (in which case Microsoft recommends a minimum stack size of 2 KB). The function DosCallback is, in effect, a call gate implemented with software that allows a ring 2 routine to call ring 3 procedures in the same application. Because a stack switch is involvedand DosCallback does not provide for the copying of any stack itemsyour program must use registers to pass any parameters for the ring 3 code. If the ring 2 and ring 3 routines communicate through variables, those variables must not lie in a ring 2 data segment (that is, a data segment that has been marked IOPL in the module definition file). Using IOPL in OS/2 Applications If you follow a few simple rules, you will find that designing and coding an OS/2 application that uses IOPL segments is a straightforward process. First, you must segregate from the rest of the application code all code that needs to enable or disable interrupts or to access I/O ports. This distinct segment becomes the IOPL segment. For compatibility with Microsoft C, give the segment the WORD and PUBLIC attributes and assign it to class 'CODE'. Be sure to choose a segment name that won't conflict with the standard Microsoft segment and group names (_TEXT, _DATA, DGROUP, and so forth). Second, for the procedures in the IOPL segment to be usable from a high level language, they should follow the OS/2 API conventions of accepting their parameters on the stack and returning their results in register AX or in variables whose addresses were passed in the original call. You must declare the procedures that contain the hardware-dependent code public and give them the far attribute because they will be entered through a call gate and must exit with a far return. The parameter to the RET instruction must be the number of bytes (not words) to be cleared from the caller's stack. If you are writing the body of your application in C, supply extern far pascal prototypes for the external IOPL routines, just as with API functions. This tells the C compiler that parameters are pushed left to right, that the called routine clears the stack, that a leading underscore should not be added to the external name, and that case in the external name can be ignored. If you are writing a MASM application, simply declare the IOPL routines as extrn far in the usual manner. Third, the initialization and termination portions of your application should include a call to the kernel API function DosPortAccess or DosCLIAccess. Use of these functions in the documented manner ensures that your application will still work as expected when the 80386-specific versions of OS/2 appear. Last, when you build the executable application, you must provide the Linker with a module definition (DEF) file which contains a SEGMENTS directive for the IOPL segment and an EXPORTS directive that declares the name and number of stack parameters for each routine in the IOPL segment that will be called from outside the segment. If you are writing your program in C, you need to compile and link in separate operations because the C compiler does not know how to pass the name of a module definition file through to the Linker. Take special care that the EXPORTS directive matches the actual code in the corresponding IOPL routine. If too few parameters are specified in the EXPORTS directive, the parameters are not all copied to the ring 2 stack, and the IOPL routine will generate a protect fault when it tries to access one of the missing items. If too many parameters are specified in the EXPORTS directive, no harm is done unless the operand for the IOPL routine's RET instruction is also incorrect. If that operand is also too large, the system clears too many items from the caller's stack when the IOPL routine exits, and a protect fault is likely to occur at a later point in the program's execution. A Sample IOPL Application The program PORTS.EXE provides a simple and practical demonstration of an OS/2 application that uses direct hardware access to read and display the first 256 I/O ports. PORTS.EXE is built from three modules: PORTS.C or PORTS.ASM, PORTIO.ASM, and PORTS.DEF. PORTIO.ASM (Figure 14-3) is the program's IOPL segment. It contains only two routines: rport and wport. The rport routine accepts a port address, reads the port, and returns the port data register in AX with the upper eight bits zeroed. The wport routine accepts a port address and a word of data, writes the lower eight bits of the data to the port, and returns nothing. Both routines use the OS/2 API conventions and can be called from either MASM or C. Notice that the code segment in this file is called IO_TEXT to differentiate it from the code segment produced by the C compiler (which has the default name _TEXT). PORTS.C (Figure 14-4 on p. 309) is the C version of the body of the application. It invokes DosPortAccess to request access to a range of I/O ports; then it calls rport to read each port and formats the data for display with printf(). Before terminating, the program again calls DosPortAccess to release the I/O ports requested earlier. PORTS.ASM (Figure 14-5 on p. 311) is the MASM equivalent of PORTS.C. title PORTIO -- Read/Write I/O Ports page 55,132 .286 ; ; PORTIO.ASM ; ; General-purpose port read/write routines for C or MASM programs. ; ; Assemble with: C> masm portio.asm; ; ; When this module is linked into a program, the following lines ; must be present in the program's module definition (DEF) file: ; ; SEGMENTS ; IO_TEXT IOPL ; ; EXPORTS ; RPORT 1 ; WPORT 2 ; ; The SEGMENTS and EXPORTS directives are recognized by the Linker ; and cause information to be built into the EXE file header for ; the OS/2 program loader. The loader is signaled to give I/O ; privilege to code executing in the segment IO_TEXT and to build ; call gates for the routines 'rport' and 'wport'. ; ; Copyright (C) 1988 Ray Duncan ; IO_TEXT segment word public 'CODE' assume cs:IO_TEXT ; RPORT: Read 8-bit data from I/O port. Port address ; is passed on stack; data is returned in register AX ; with AH zeroed. Other registers are unchanged. ; ; C syntax: unsigned port, data; ; data = rport(port); public rport rport proc far push bp ; save registers and mov bp,sp ; set up stack frame push dx mov dx,[bp+6] ; get port number in al,dx ; read the port xor ah,ah ; clear upper 8 bits pop dx ; restore registers pop bp ret 2 ; discard parameters, ; return port data in AX rport endp ; WPORT: Write 8-bit data to I/O port. Port address and ; data are passed on stack. All registers are unchanged. ; ; C syntax: unsigned port, data; ; wport(port, data); public wport wport proc far push bp ; save registers and mov bp,sp ; set up stack frame push ax push dx mov ax,[bp+6] ; get data to write mov dx,[bp+8] ; get port number out dx,al ; write the port pop dx ; restore registers pop ax pop bp ret 4 ; discard parameters, ; return nothing wport endp IO_TEXT ends end Figure 14-3. PORTIO.ASM, the source code for the IOPL segment which contains the routines to read and write I/O ports. /* PORTS.C An OS/2 IOPL demonstration program that reads and displays the first 256 I/O ports. Requires the separate module PORTIO.ASM. Compile with: C> cl /c ports.c Link with: C> link ports+portio,ports,,os2,ports Usage is: C> ports Copyright (C) 1988 Ray Duncan */ #include #define API extern far pascal unsigned API rport(unsigned); /* function prototypes */ void API wport(unsigned, unsigned); void API DosSleep(unsigned long); unsigned API DosPortAccess(unsigned, unsigned, unsigned, unsigned); /* parameters for DosPortAccess */ #define REQUEST 0 /* request port */ #define RELEASE 1 /* release port */ #define BPORT 0 /* beginning port */ #define EPORT 255 /* ending port */ main(int argc, char *argv[]) { int i; /* scratch variable */ /* request port access */ if(DosPortAccess(0, REQUEST, BPORT, EPORT)) { printf("\nDosPortAccess failed.\n"); exit(1); } printf("\n "); /* print title line */ for(i=0; i<16; i++) printf(" %2X", i); for(i=BPORT; i<=EPORT; i++) /* loop through all ports */ { if((i & 0x0f)==0) { printf("\n%04X ", i); /* new line needed */ } printf(" %02X", rport(i)); /* read and display port */ } DosPortAccess(0, RELEASE, BPORT, EPORT); /* release port access */ } Figure 14-4. PORTS.C, the C source code for the body of the PORTS.EXE example program. title PORTS -- IOPL Demo Program page 55,132 .286 ; ; PORTS.ASM ; ; An OS/2 IOPL demonstration program that reads and displays the ; first 256 I/O ports. Requires the separate module PORTIO.ASM. ; ; Assemble with: C> masm ports.asm; ; Link with: C> link ports+portio,ports,,os2,ports ; ; Usage is: C> ports ; ; Copyright (C) 1988 Ray Duncan ; cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed blank equ 20h ; ASCII space code stdout equ 1 ; standard output handle stderr equ 2 ; standard error handle bport equ 0 ; first port to display eport equ 255 ; last port to display request equ 0 ; request port access release equ 1 ; release port access extrn DosPortAccess:far ; kernel API functions extrn DosWrite:far extrn DosExit:far extrn rport:far ; PORTIO.ASM functions extrn wport:far DGROUP group _DATA _DATA segment word public 'DATA' wlen dw 0 ; actual number of bytes ; written by DosWrite msg1 db cr,lf ; error message db 'DosPortAccess failed.' db cr,lf msg1_len equ $-msg1 msg2 db cr,lf ; heading db ' 0 1 2 3 4 5 6' db ' 7 8 9 A B C D E F' msg2_len equ $-msg2 msg3 db cr,lf ; display port number msg3a db 'NNNN ' msg3_len equ $-msg3 msg4 db ' ' ; display port data msg4a db 'NN' msg4_len equ $-msg4 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 push ds ; make DGROUP addressable pop es ; with ES too ; request port access... push 0 ; reserved push request ; request/release push bport ; first port number push eport ; last port number call DosPortAccess ; transfer to OS/2 or ax,ax ; call successful? jz main1 ; yes, jump ; display error message... push stderr ; standard error handle push ds ; message address push offset DGROUP:msg1 push msg1_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; now terminate process... push 1 ; terminate all threads push 1 ; return code = 1 (error) call DosExit ; transfer to OS/2 main1: ; print heading... push stdout ; standard output handle push ds ; address of heading push offset DGROUP:msg2 push msg2_len ; length of heading push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 mov dx,bport ; initialize port number main2: test dx,0fh ; new line needed? jnz main3 ; no, jump mov ax,dx ; convert port number mov di,offset DGROUP:msg3a ; to ASCII for display call wtoa ; display port number... push stdout ; standard output handle push ds ; message address push offset DGROUP:msg3 push msg3_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 main3: push dx ; call 'rport' to read port call rport ; returns AX = data mov di,offset msg4a ; convert port data call btoa ; to ASCII for display ; display port data... push stdout ; standard output handle push ds ; message address push offset DGROUP:msg4 push msg4_len ; message length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 inc dx ; increment port number cmp dx,eport ; done with all ports? jbe main2 ; not yet, read another ; release port access... push 0 ; reserved push release ; request/release push bport ; first port number push eport ; last port number call DosPortAccess ; transfer to OS/2 ; final exit to OS/2... push 1 ; terminate all threads push 0 ; return code = 0 (success) call DosExit ; transfer to OS/2 main endp ; WTOA: Convert word to hex ASCII ; ; Call with: AX = data to convert ; ES:DI = storage address ; Returns: nothing ; Uses: AX, CL, DI wtoa proc near push ax ; save original value mov al,ah call btoa ; convert upper byte pop ax ; restore original value call btoa ; convert lower byte ret ; back to caller wtoa endp ; BTOA: Convert byte to hex ASCII ; ; Call with: AL = data to convert ; ES:DI = storage address ; Returns: nothing ; Uses: AX, CL, DI btoa proc near sub ah,ah ; clear upper byte mov cl,16 ; divide by 16 div cl call ascii ; convert quotient stosb ; store ASCII character mov al,ah call ascii ; convert remainder stosb ; store ASCII character ret ; back to caller btoa endp ; ASCII: Convert nibble to hex ASCII ; ; Call with: AL = data to convert in low 4 bits ; Returns: AL = ASCII character ; Uses: nothing ascii proc near add al,'0' ; add base ASCII value cmp al,'9' ; is it in range 0 through 9? jle ascii2 ; jump if it is add al,'A'-'9'-1 ; no, adjust for range A-F ascii2: ret ; return ASCII character in AL ascii endp _TEXT ends end main ; defines entry point Figure 14-5. PORTS.ASM, the MASM source code for the body of the PORTS.EXE example program. PORTS.DEF (Figure 14-6 on the following page) is the module definition file. The SEGMENTS directive marks IO_TEXT as an IOPL segment. The EXPORTS directive defines the number of stack parameters for RPORT and WPORT and identifies the two routines as callable from outside the IOPL segment. NAME PORTS WINDOWCOMPAT PROTMODE STACKSIZE 4096 SEGMENTS IO_TEXT IOPL EXPORTS RPORT 1 WPORT 2 Figure 14-6. PORTS.DEF, the module definition file for the PORTS.EXE demonstration program. Omit the STACKSIZE directive if you are linking the C version. To build the executable program file PORTS.EXE from the source files PORTS.C, PORTIO.ASM, and PORTS.DEF, enter the following commands: [C:\] CL /c PORTS.C [C:\] MASM PORTIO.ASM; [C:\] LINK PORTS+PORTIO,PORTS,,OS2,PORTS To build PORTS.EXE from the source files PORTS.ASM, PORTIO.ASM, and PORTS.DEF, enter the commands: [C:\] MASM PORTS.ASM; [C:\] MASM PORTIO.ASM; [C:\] LINK PORTS+PORTIO,PORTS,,OS2,PORTS Figure 14-7 shows a sample of the output for PORTS.EXE. If you get the error message OS/2 is not presently configured to run this application, add the statement IOPL=YES to your CONFIG.SYS file and reboot the system. [C:\] PORTS 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 00 00 00 00 AC FF 00 00 00 FF FF FF FF 00 FF FF 0010 00 00 00 00 AC FF 00 00 00 FF FF FF FF 00 FF FF . . . 00E0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00F0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF [C:\] Figure 14-7. Sample output of PORTS.EXE. Chapter 15 Time, Date, and Timers Because OS/2 is a preemptive multitasking system, it is both time-driven and time-sensitive. Its time-related responsibilities include: Dividing the available CPU cycles (time-slicing) among the active threads according to their priority Detecting I/O timeouts Providing user-programmable timer events Maintaining the current date and current time of day Stamping the directory entries for files with the date and time they were created or last modified On PC/AT and PS/2 compatible machines, OS/2 relies on two hardware components to carry out these duties: a battery-powered clock/calendar chip and a programmable counter/timer chip. The battery-powered clock/calendar chip runs autonomously, separate from the rest of the computer system. Whenever the system is turned on or restarted, OS/2 initializes its internal time and date by reading the I/O ports that provide access to this chip. You can update the clock/calendar chip with the TIME and DATE commands or with the SETUP program. The clock/calendar chip is also programmed during system initialization to generate a hardware interrupt (called the "timer tick") at intervals of 31 milliseconds (approximately 32 Hz). This is the interval used by Microsoft and IBM OS/2 versions 1.0 and 1.1. The interval might be different in other OEM versions. Each time a timer tick occurs, the currently executing thread is interrupted, and control is transferred to the clock driver's interrupt handler. The handler updates the system's internal time, date, and millisecond counters and then notifies the other kernel and driver modules of the timer tick by means of a special kernel dispatcher. Finally, the clock driver transfers control to the scheduler, which examines its list of eligible threads and selects one for execution. The programmable counter/timer chip (an Intel 8253 or equivalent) is set up during system initialization to provide interrupts at 55-millisecond intervals (18.2 Hz). This interrupt is masked off when any protected mode session is in the foreground. When the real mode session is in the foreground, the counter/timer chip's interrupt is enabled, serviced on Int 08H (IRQ0), and distributed to real mode applications via Int 1CH for compatibility with MS-DOS. In addition to maintaining the date and the time and servicing periodic timer interrupts for its own purposes, OS/2 exports API functions that allow application programs to do the following: Obtain or set the system time and date Suspend execution for a predetermined interval Be notified by means of a semaphore when a specified interval elapses These API functions are summarized in Figure 15-1. The functions to suspend execution or to report elapsed time with semaphores accept their time parameters in milliseconds (msec.). These values are always rounded up to a multiple of the timer tick interval and are accurate only to within one or two timer ticks. The length of a timer tick interval is available in the global information segment (see p. 524). Because this value is OEM-dependent, programs should obtain it at run time rather than having it hard-coded. Function Description DosGetInfoSeg Obtains selectors for global and local information segments; global information segment includes current date, time, time zone, and day of the week DosGetDateTime Returns current date, time, time zone, and day of the week DosSetDateTime Sets system date and time DosSleep Suspends execution of a thread for a programmed interval DosTimerAsync Starts one-shot asynchronous timer that clears system semaphore DosTimerStart Starts periodic asynchronous timer that clears system semaphore DosTimerStop Disables timer previously activated with DosTimerAsync or DosTimerStart Figure 15-1. The OS/2 API function calls for time, date, and programmable timers. Reading and Setting Date and Time Your application program can determine the current system time and date in two ways. The method you use depends on the characteristics of your program. For the average applications program, the preferred method is to use DosGetInfoSeg to get a selector for the global information segment. Information related to the current date and time is located at specific offsets within the segment, as shown in Figure 15-2. An actual memory dump of the global information segment is in Figure 15-3. Offset Length Contents 00H 4 Elapsed time from 1-1-1970 in seconds 04H 4 Milliseconds since system boot 08H 1 Hours (023) 09H 1 Minutes (059) 0AH 1 Seconds (059) 0BH 1 Hundredths of a second (099) 0CH 2 Time zone (minutes GMT) (-1 = undefined) 0EH 2 Timer interval (units = 0.0001 seconds) 10H 1 Day (131) 11H 1 Month (112) 12H 2 Year (1980+) 14H 1 Day of the week (0 = Sunday, 1 = Monday, etc.) Figure 15-2. Format of current date and time in global information segment. Month Weekday Hours Minutes 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0060:0000 B9 3657 20 358D 0E 00 16 1D 0D 4E FF FF 36 01 .6W 5......N..6. 0060:0010 0C 03 C3 07 04 05 00 00 04 10 1000 13 00 01 03 ................ 0060:0020 1F00 F800 01 00 00 00 00 00 0000 00 00 00 00 ................ 0060:0030 0000 0000 00 00 00 00 00 00 0000 00 00 00 00 ................ 0060:0040 0000 0000 00 00 ...... Day Year Seconds Figure 15-3. A hex and ASCII dump of a global information segment. When the selector for the global information segment is in hand, your program can inspect the date or time as often as necessary without the overhead of additional calls to OS/2 (Figure 15-4 on the following page). Be careful, however, not to be misled by the occurrence of a rollover in one of the fields while your program is preempted by an interrupt handler or another thread. To protect against this, read the fields of interest repeatedly until you get two sets that are the same. An alternative (but strongly discouraged) technique is to use an IOPL routine that disables interrupts, reads the global information segment values, and then reenables interrupts. ; fields in global info segment... hour equ 08h ; hours min equ 09h ; minutes sec equ 0ah ; seconds cseg equ 0bh ; hundredths of a second gseg dw ? ; receives selector for ; global information segment lseg dw ? ; receives selector for ; local information segment . . . ; get info seg selectors... push ds ; receives global selector push offset DGROUP:gseg push ds ; receives local selector push offset DGROUP:lseg call DosGetInfoSeg ; transfer to OS/2 or ax,ax ; were selectors attained? jnz error ; jump if function failed ; else get current time... mov es,gseg ; load selector for global ; information segment ; let AL = hours, AH = minutes label1: mov ax,word ptr es:hour ; let BL = seconds, BH = centiseconds mov bx,word ptr es:sec ; read time again... mov cx,word ptr es:hour mov dx,word ptr es:sec cmp ax,cx ; check for rollover... jne label1 ; rollover occurred, start over cmp bx,dx jne label1 ; rollover occurred, start over . . . Figure 15-4. Obtaining the selector for the global information segment with DosGetInfoSeg and then using that selector to obtain the current time. The second method for getting the date and time is ordinarily used only by programs that need to obtain these values infrequently or that need to alter the date or time. It involves use of the function DosGetDateTime, which is called with the address of an 11-byte structure. OS/2 fills in the structure with the current date, time, time zone, and day of the week in the format shown in Figure 15-5. Because this method requires an operating system call each time the date or time is inspected, it is considerably slower than using the global information segment. It is useful mainly because the organization of the structure is exactly the same as for DosSetDateTime. When a program needs to modify some part of the time or date, it can first call DosGetDateTime to fill the structure with valid information, make any necessary changes, and then write the updated information back to the operating system with DosSetDateTime (Figure 15-6 on the following page). Offset Length Contents 00H 1 Hours (023) 01H 1 Minutes (059) 02H 1 Seconds (059) 03H 1 Hundredths of a second (099) 04H 1 Day (131) 05H 1 Month (112) 06H 2 Year (1980+) 08H 2 Time zone (minutes GMT) (-1 = undefined) 0AH 1 Day of week (0 = Sunday, 1 = Monday, etc.) Figure 15-5. Format of an 11-byte structure used by both DosGetDateTime and DosSetDateTime to hold date and time information. dtinfo label byte ; date/time information hour db 0 ; 0 min db 0 ; 1 sec db 0 ; 2 csec db 0 ; 3 day db 0 ; 4 mon db 0 ; 5 year dw 0 ; 6 zone dw 0 ; 8 minutes +/- GMT dow db 0 ; 10 day of week . . . ; first read date/time... push ds ; receives date/time info push offset DGROUP:dtinfo call DosGetDateTime ; transfer to OS/2 or ax,ax ; did read succeed? jnz error ; jump if function failed mov hour,12 ; set time to 12:00 mov min,0 ; clear minutes mov sec,0 ; and seconds mov csec,0 ; and hundredths of a second ; now set date/time... push ds ; address of structure push offset DGROUP:dtinfo call DosSetDateTime ; transfer to OS/2 or ax,ax ; was time/date set? jnz error ; jump if function failed . . . Figure 15-6. Example of altering the system time with DosSetDateTime, using DosGetDateTime first to obtain all the other current information. In this example the system time is set to 12:00:00:00 (noon). Programmed Delays The function DosSleep allows a thread to suspend itself for a programmed interval. Using DosSleep, a thread can make itself dormant so that it doesn't tie up CPU resources, yet wake up periodically and check for an event, poll a device, or find out if a previously unavailable resource (a file, for example, that was locked by another process) has been freed up so that the thread can continue. Only the thread that calls DosSleep is suspended; other threads in the same process are not affected. DosSleep is one of the simplest functions to use in the entire OS/2 API. Its only argument is an unsigned double precision number of milliseconds, allowing an upper limit on the delay time of about 4.29 * 10^6 seconds (1193 hours). Under normal circumstances this function cannot return an error. Figure 15-7 illustrates the use of DosSleep by a thread that displays the system time at approximately 1-second intervals. A program should not rely on the DosSleep function if it needs to resume execution at an exact time. The delay produced by a DosSleep call is accurate only to the granularity of the timer tick interval; its duration will always be at least the requested interval, rounded up to the next clock tick. In addition, the requested interval determines only the time at which the thread is again marked runnable; it does not guarantee that the thread will run at that time. The revived execution of the thread can be delayed unpredictably by other events in the system, such as servicing of hardware interrupts or the activity of other, higher-priority threads. In general, using DosSleep to obtain clocklike behavior is inevitably unsatisfactory because of the cumulative effect of delays and rounding errors. An interesting special use of DosSleep is to yield processor time explicitly. If the DosSleep parameter is zero, the thread gives up the remainder of its current timeslice but is rescheduled normally for its next timeslice. . . . showit: call display ; update clock display ; suspend for 1 second... push 0 ; push double precision push 1000 ; value of 1000 msec. call DosSleep ; transfer to OS/2 or ax,ax ; unexpected error? jnz error ; jump if function failed jmp showit ; now display time again... . . . Figure 15-7. A simple clock utility, which uses the DosSleep function to suspend itself for 1 second and then displays the current time. Because DosSleep is subject to rounding errors, and because reactivation of the thread can be delayed by other activity in the system, the display does not occur at precise 1-second intervals. Using Timers Two OS/2 functions, DosTimerAsync and DosTimerStart, provide for an asynchronous signal to a process after a programmed delay. The timers communicate with the process by clearing a system semaphore. DosTimerAsync sets up a "one-shot" timer; DosTimerStart establishes a "periodic" timer that continues to clear the semaphore at the requested interval until the timer is disabled. A process that uses timers must first create a system semaphore and initialize it to a "set" state (see Chapter 13). The process then calls DosTimerAsync or DosTimerStart to create and start the timer. Both functions require the same three parameters: a system semaphore handle, an unsigned double precision timer interval, and the address of a variable. If the timer is successfully created, the system returns a timer handle to the variable; this handle can be used later with DosTimerStop to disable the timer. Once the timer is armed, the process can go on to other work. A thread within the process can synchronize with the timer by calling DosMuxSemWait (preferred) or DosSemWait with a timeout value of -1 to block on the semaphore. Alternatively, it can test for the expiration of the timer at suitable intervals by calling DosSemWait with a timeout value of 0 causing DosSemWait to return immediately with an error code if the semaphore is set. To synchronize with a periodic timer, the process must reset the semaphore immediately after the timer clears it so that it will not miss subsequent firings of the timer. It is, of course, quite practical to control multiple threads with the same timerall threads in a process have equal access to any system semaphores created or opened by that process. When a process is using a periodic timer, one thread should block on the semaphore with DosSemWait and be responsible for resetting the semaphore; by promoting this thread to a "time-critical" priority, you can ensure that its execution will never be suppressed by other activity in the system. The other threads should run at normal priority and block on the semaphore with DosMuxSemWait (which is the only semaphore function that is edge-triggered rather than level-triggered). Figure 15-8 demonstrates the use of a periodic timer to display the system time at 1-second intervals. This approach is superior to the use of DosSleep shown in Figure 15-7 because the overhead of the time display procedure is "hidden" inside the interval controlled by the timer. However, the timer is still subject to rounding errors and to delays caused by hardware interrupt processing or the activity of other, higher-priority threads. sname db '\SEM\TIMER.SEM',0 shandle dd 0 ; semaphore handle thandle dw 0 ; timer handle . . . ; create system semaphore... push 1 ; 1 = not exclusive ownership push ds ; receives semaphore handle push offset DGROUP:shandle push ds ; address of semaphore name push offset DGROUP:sname call DosCreateSem ; transfer to OS/2 or ax,ax ; semaphore created? jnz error ; jump if function failed... ; create periodic timer... push 0 ; interval = 1000 msec. push 1000 push word ptr shandle+2 ; semaphore handle push word ptr shandle push ds ; receives timer handle push offset DGROUP:thandle call DosTimerStart ; transfer to OS/2 or ax,ax ; timer created? jnz error ; jump if function failed showit: ; set the semaphore... push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosSemSet ; transfer to OS/2 or ax,ax ; did set succeed? jnz error ; jump if function failed call display ; update clock display ; now block on semaphore... push word ptr shandle+2 ; semaphore handle push word ptr shandle push -1 ; timeout = -1 push -1 ; (wait indefinitely) call DosSemWait ; transfer to OS/2 or ax,ax ; unexpected error? jnz error ; jump if wait failed jmp showit ; now display time again... . . . Figure 15-8. Another simple clock timer, similar to that in Figure 15-7, but which uses a periodic timer and system semaphore (rather than DosSleep) to trigger display of the clock. The Clock Device The system's device driver for the real time clock masquerades as a character device driver and has the logical name CLOCK$. The kernel identifies the clock driver by a special bit in the attribute word of its header, so the device name has no special significance to the system itself. If you use DosOpen to obtain a handle for the CLOCK$ device, you can use ordinary DosRead and DosWrite calls to read from it and write data to it. This data must be in a special 6-byte format: Offset Length Contents 00H 2 Days since January 1, 1980 02H 1 Minutes (059) 03H 1 Hours (023) 04H 1 Hundredths of a second (099) 05H 1 Seconds (059) This is the same data format used by the real time clock driver in MS-DOS and PC-DOS systems. There is no particular reason to use this CLOCK$ driver capability instead of DosGetDateTime or inspection of the global information segment; it is mentioned here only for completeness. SECTION 5 CUSTOMIZING OS/2 Chapter 16 Filters A filter is, essentially, a program that operates on a stream of characters. The source and the destination of the character stream can be files, another program (by means of a pipe), or almost any character device. The transformation applied by the filter to the character stream can be as simple as character substitution or as complex as fitting a curve to a set of coordinates. The standard OS/2 package includes three simple filters: SORT, which alphabetically sorts text line by line; FIND, which searches a text stream to match a specified string; and MORE, which displays text one screenful at a time. System Support for Filters The operation of a filter program relies on two OS/2 features that are modeled after UNIX/XENIX: standard devices and redirectable I/O. The standard devices are represented by three handles that are established by the command interpreter (CMD.EXE) and inherited by each newly created process from its immediate parent. Thus, the standard device handles are already opened when a process begins its execution, and the process can use them for read and write operations without further preliminaries. The default assignments of the standard device handles are as follows: Handle Name Default Device 0 stdin (standard input) KBD$ 1 stdout (standard output) SCREEN$ 2 stderr (standard error) SCREEN$ When the user executes a program by entering its name at the system (CMD.EXE) prompt or in a batch file, any or all of the three standard device handles can be redirected from its default device to another file, to a character device, or to a process. This redirection is accomplished by including one of the six special directives <, >, >>, 2>, 2>>, or | on the command line, as illustrated in Figure 16-1. The following command, for example, causes the SORT filter to read its input from the file MYFILE.TXT, sort the lines alphabetically, and write resulting text to the character device PRN (the logical name for the system's list device): [C:\] SORT PRN The actions requested by the redirection parameters take place at the level of CMD.EXE and are invisible to the programs they affect. As shown at the end of this chapter, any other process can also institute such redirection. A program that uses Kbd or Vio calls for higher performance is unaffected by redirection directives on the command line. The same is true of a program that calls DosOpen for KBD$, SCREEN$, or CON and uses the resulting handle for I/O. This independence can frustrate a user who is trying to drive a program with a script or capture its output into a filethe program doesn't behave "as expected." A well-behaved program should call DosQHandType to determine if one or more of its standard devices have been redirected, then use DosRead and DosWrite with the standard handles (rather than the Kbd and Vio subsystems) if redirection is in effect. Syntax Resulting Redirection < file The program takes its standard input from the specified file or device instead of from the keyboard. > file The program sends its standard output to the specified file or device instead of to the display (also 1> file). >> file The program appends its standard output to the current contents of the specified file instead of sending it to the display (also 1>> file). 2> file The program sends its standard error output to the specified file or device instead of to the display. 2>> file The program appends its standard error output to the current contents of the specified file instead of sending it to the display. p1 | p2 The standard output of process p1 is routed to become the standard input of process p2. (The output of p1 is said to be piped.) Figure 16-1. Command syntax for redirecting standard device handles. How Filters Work By convention, a filter program reads its text from the standard input device and writes the results of its operations to the standard output device. When the end of the input stream is reached, the filter simply terminates. As a result, filters are both flexible and simple. The flexibility of filter programs derives from their indifference to the source of the data they process or the destination of their output. Any character device that has a logical name within the system (CON, COM1, COM2, PRN, LPT1, LPT2, LPT3, and so on), any file on any block device (local or network) known to the system, or any other program can supply input to a filter or accept its output. Although flexible, filters remain simple because they rely on their parent processes to supply standard input and standard output handles that have already been appropriately redirected. The parent is responsible for opening or creating any necessary files, checking the validity of logical character device names, and loading and executing the preceding or following process in a pipe. The filter can concern itself strictly with operating on the data; it can leave the I/O details to the operating system and to its parent. Building a Filter Creating a new filter for OS/2 is straightforward. In its simplest form, a filter need use only DosRead and DosWrite to get characters from standard input and to send them to standard output; it operates on the text stream on a character-by-character or line-by-line basis. Figures 16-2 and 16-3 contain template filters in both assembly language (PROTO.ASM) and C (PROTO.C). The C and assembler versions run at roughly the same speed, although the C version is several times larger. Both can be assembled or compiled, linked, and run exactly as shown. Of course, in this form they simply function like a slow COPY command. title PROTO.ASM -- Filter Template page 55,132 .286 ; ; PROTO.ASM ; ; MASM filter template for OS/2. ; ; Assemble with: C> masm proto.asm; ; Link with: C> link proto,,,os2; ; ; Usage is: C> proto destination ; ; Copyright (C) 1988 Ray Duncan ; stdin equ 0 ; standard input device stdout equ 1 ; standard output device stderr equ 2 ; standard error device cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed bufsize equ 256 ; I/O buffer size extrn DosRead:far extrn DosWrite:far extrn DosExit:far DGROUP group _DATA ; 'automatic data group' _DATA segment word public 'DATA' input db bufsize dup (?) ; storage for input line output db bufsize dup (?) ; storage for output line rlen dw ? ; receives bytes read wlen dw ? ; receives bytes written _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 push ds ; make DGROUP addressable pop es ; via ES register too assume es:DGROUP cld ; safety first main1: ; read line from standard input... push stdin ; standard input handle push ds ; buffer address push offset DGROUP:input push bufsize ; buffer length push ds ; receives bytes read push offset DGROUP:rlen call DosRead ; transfer to OS/2 or ax,ax ; was read successful? jnz main3 ; exit if any error mov ax,rlen ; get length of input or ax,ax ; any characters read? jz main2 ; jump if end of stream call translate ; translate line if necessary or ax,ax ; anything to output? jz main1 ; no, go read another line ; write line to standard output... push stdout ; standard output handle push ds ; buffer address push offset DGROUP:output push ax ; buffer length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 or ax,ax ; write successful? jnz main3 ; exit if any error jmp main1 ; go read another line main2: ; end of stream reached push 1 ; 1 = end all threads push 0 ; 0 = exit code call DosExit ; final exit to OS/2 main3: ; error encountered push 1 ; 1 = end all threads push 1 ; 1 = exit code call DosExit ; final exit to OS/2 main endp ; end of main procedure ; Perform any necessary translation on line stored in ; 'input' buffer, leaving result in 'output' buffer. ; ; Call with: AX = length of data in 'input' buffer ; ; Return: AX = length to write to standard output ; ; Action of template routine is simply to copy the line. translate proc near ; copy input to output... mov si,offset DGROUP:input mov di,offset DGROUP:output mov cx,ax rep movsb ret ; return AX = length unchanged translate endp _TEXT ends end main ; program entry point Figure 16-2. Assembly language source code for PROTO.ASM, a template for a filter. /* PROTO.C A filter template for OS/2. Compile with: C> cl proto.c Usage is: C> proto destination Copyright (C) 1988 Ray Duncan */ #include #define STDIN 0 /* standard input handle */ #define STDOUT 1 /* standard output handle */ #define BUFSIZE 256 /* I/O buffer size */ #define API unsigned extern far pascal API DosRead(unsigned, void far *, unsigned, unsigned far *); API DosWrite(unsigned, void far *, unsigned, unsigned far *); static char input[BUFSIZE]; /* buffer for input line */ static char output[BUFSIZE]; /* buffer for output line */ main(int argc,char *argv[]) { int rlen, wlen; /* scratch variables */ while(1) /* do until end of file */ { /* get line from standard input stream */ if(DosRead(STDIN, input, BUFSIZE, &rlen)) exit(1); /* exit if read error */ if(rlen == 0) exit(0); /* exit if end of stream */ /* write translated line to standard output stream */ if(DosWrite(STDOUT, output, translate(rlen), &wlen)) exit(1); /* exit if write error */ } } /* Perform any necessary translation on input line, leaving the resulting text in output buffer. Returns length of translated line (may be zero). */ int translate(int length) { memcpy(output,input,length); /* template action is */ /* to copy input line */ return(length); /* and return its length */ } Figure 16-3. C language source code for PROTO.C, a template for a filter. To obtain a filter that does something useful, you must insert between the reads and writes a routine that performs some modification of the text stream that is flowing by. For example, Figures 16-4 and 16-5 contain the source code for a new translate function that you can substitute for the original translate in PROTO.ASM and PROTO.C. After this alteration, the filter converts all uppercase input characters (AZ) to lowercase (az) output, leaving other characters unchanged. ; ; Copy input buffer to output buffer, translating ; all uppercase characters to lowercase. ; ; Call with: AX = length of data in 'input' buffer ; ; Return: AX = length to write to standard output ; translate proc near push ax ; save original length ; address of original line mov si,offset DGROUP:input ; address for converted line mov di,offset DGROUP:output mov cx,ax ; length to convert jcxz trans3 ; exit if empty line trans1: ; convert character by character lodsb ; get input character cmp al,'A' ; if not A-Z, leave it alone jb trans2 cmp al,'Z' ja trans2 add al,'a'-'A' ; it's A-Z, convert it trans2: stosb ; put into output buffer loop trans1 ; loop until all characters ; have been transferred trans3: pop ax ; get back line length ret ; and return it in AX translate endp Figure 16-4. A substitute assembly language translation routine to create a lowercasing filter. /* Translate uppercase characters to lowercase characters, leaving resulting text in output buffer and returning its length. */ int translate(int length) { int i; /* scratch index */ memcpy(output,input,length); /* copy input to output */ for(i = 0; i < length; i++) /* lowercase the output */ { if(output[i] >= 'A' && output[i] <= 'Z') output[i] += 'a'-'A'; } return(length); /* return its length */ } Figure 16-5. A substitute C language translation routine to create a lowercasing filter. As another example, Figure 16-6 contains the C source code for a line-oriented filter called FIND. This filter (which is similar to the FIND filter supplied with OS/2 but much simpler) is invoked with a command line in the following form: FIND "pattern" [destination] FIND searches the input stream for lines containing the pattern specified on the command line. The line number and text of any line containing a match is sent to standard output, with any tabs expanded to 8-column stops. Unlike PROTO.C, which performed direct OS/2 function calls with DosRead and DosWrite, the FIND filter uses the C runtime library's gets() and printf() functions. This makes formatting of the output easier but drastically enlarges the size of the executable file. The FIND program in Figure 16-6 differs from the FIND filter in the OS/2 retail package in several respects. First, it is not case-sensitive, so the pattern "foobar" will match "FOOBAR," "FooBar," and so forth. Second, this filter supports no switches; these are left as entertainment for the reader. Third, this program always reads from the standard inputit cannot open its own files. /* FIND.C Searches text stream for a string. Compile with: C> cl find.c Usage is: C> find "pattern" [destination] Copyright (C) 1988 Ray Duncan */ #include #define TAB '\x09' /* ASCII tab (^I) */ #define BLANK '\x20' /* ASCII space */ #define TAB_WIDTH 8 /* columns per tab stop */ #define BUF_SIZE 256 static char input[BUF_SIZE]; /* input line buffer */ static char output[BUF_SIZE]; /* output line buffer */ static char pattern[BUF_SIZE]; /* search pattern buffer */ void writeline(int, char *); /* function prototype */ main(int argc, char *argv[]) { int line = 0; /* initialize line variable */ if(argc < 2) /* search pattern supplied? */ { puts("find: missing pattern"); /* exit if no search pattern */ exit(1); } strcpy(pattern,argv[1]); /* save copy of search pattern */ strupr(pattern); /* fold it to uppercase */ while(gets(input) != NULL) /* read a line from input */ { line++; /* count lines */ strcpy(output, input); /* save copy of input string */ strupr(input); /* fold input to uppercase */ if(strstr(input, pattern)) /* if line contains pattern */ writeline(line, output); /* write it to standard output */ } exit(0); /* terminate at end of file */ } /* WRITELINE: Write line number and text to standard output, expanding any tab characters to stops defined by TAB_WIDTH. */ void writeline(int line, char *p) { int i = 0; /* index to input line */ int col = 0; /* output column counter */ printf("\n%4d: ", line); /* write line number */ while(p[i] != NULL) /* while not end of line */ { if(p[i] == TAB) /* if tab, expand it */ { do putchar(BLANK); while((++col % TAB_WIDTH) != 0); } else /* otherwise leave it alone */ { putchar(p[i]); /* send character */ col++; /* count columns */ } i++; /* advance through input */ } } Figure 16-6. C source code for FIND.C, a FIND filter. This simple filter searches each line of a text stream for a pattern and sends the matching lines to the standard output. Using a Filter as a Child Process An application program can load and execute a filter as a child process to carry out a specific task instead of incorporating all the code necessary to do the same job itself. Before the child filter is loaded, the parent must arrange for the redirection of the standard input and the standard output handles (which the child inherits) to the files or to the character devices that will supply the filter's input and receive its output. This redirection is accomplished in the steps that follow. 1. The parent process uses DosDupHandle to create duplicates of its own standard input and standard output handles and saves the duplicates. 2. The parent uses DosOpen to open or create the files or devices that the child process will use for input and output. 3. The parent uses DosDupHandle to force its standard device handles to track the new file or device handles acquired in step 2. 4. The parent uses DosExecPgm with the synchronous option to load and execute the child process. The child inherits the redirected standard input and standard output handles and uses them to do its work. The parent regains control after the filter terminates. If the parent uses one of DosExecPgm's asynchronous options, it can proceed with other work while the child process is running and then resynchronize with the child by calling DosCwait. 5. The parent uses the duplicate handles created in step 1, together with DosDupHandle, to restore its own standard input and standard output handles to their original meanings. 6. The parent closes (with DosClose) the duplicate handles created in step 1 because they are no longer needed. It might seem as though the parent process could just as easily close its own standard input and standard output (handles 0 and 1), open the input and output files needed by the child, run the child process, close the files upon regaining control, and then reopen the CON device twice. Because the open operation always assigns the first free handle, this approach would have the desired effect as far as the child process is concerned. However, it would throw away any redirection that had been established for the parent process by its parent. Thus, the need to preserve any preexisting redirection of the parent's standard input and standard output, along with the desire to preserve the parent's usual output channel for messages right up to the actual point of the DosExecPgm call, is the reason for the elaborate procedure outlined above. The program EXECSORT.ASM in Figure 16-7 demonstrates this redirection of input and output for a filter run as a child process. EXECSORT makes duplicates of its standard input and standard output handles with DosDupHandle; then it redirects the standard input to the file MYFILE.DAT (which it opens) and the standard output to the file MYFILE.SRT (which it creates). EXECSORT then calls DosExecPgm to run the SORT.EXE filter that is supplied with OS/2. The SORT program reads the file MYFILE.DAT via its standard input handle, sorts the file alphabetically by line, and writes the sorted data to MYFILE.SRT via its standard output handle. When SORT terminates, OS/2 closes SORT's inherited handles for the standard input and standard output, which forces an update of the directory entry for the file MYFILE.SRT. EXECSORT then resumes execution, restores its standard input and standard output handles (which are still open) to their original meanings with DosDupHandle, displays a success message on the standard output, and exits to OS/2. title EXECSORT -- Run SORT.EXE as Child page 55,132 .286 .sall ; ; EXECSORT.ASM ; ; Demonstration of use of DosExecPgm to run the OS/2 filter SORT.EXE ; as a child process, redirecting its input to MYFILE.DAT and its ; output to MYFILE.SRT. ; ; Assemble with: C> masm execsort.asm; ; Link with: C> link execsort,,,os2,execsort ; ; Usage is: C> execsort ; ; Copyright (C) 1988 Ray Duncan ; stdin equ 0 ; standard input device stdout equ 1 ; standard output device stderr equ 2 ; standard error device cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed extrn DosClose:far extrn DosDupHandle:far extrn DosExecPgm:far extrn DosExit:far extrn DosOpen:far extrn DosWrite:far jerr macro target ;; Macro to test AX local zero ;; and jump if AX nonzero or ax,ax jz zero ;; Uses JMP DISP16 to avoid jmp target ;; branch out of range errors zero: endm DGROUP group _DATA _DATA segment word public 'DATA' iname db 'MYFILE.DAT',0 ; name of input file oname db 'MYFILE.SRT',0 ; name of output file ihandle dw ? ; handle for input file ohandle dw ? ; handle for output file action dw ? ; receives DosOpen action oldin dw -1 ; dup of old stdin handle oldout dw -1 ; dup of old stdout handle newin dw stdin ; forced to track ihandle newout dw stdout ; forced to track ohandle pname db 'SORT.EXE',0 ; pathname of SORT filter objbuff db 64 ; receives failing dynlink objbuff_len equ $-objbuff pcodes dw 0,0 ; PID, exit code of child msg db cr,lf,'SORT was executed as child.',cr,lf msg_len equ $-msg _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 ; prepare stdin and stdout ; handles for child SORT... ; dup handle for stdin... push stdin ; standard input handle push ds ; receives new handle push offset DGROUP:oldin call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error ; dup handle for stdout... push stdout ; standard output handle push ds ; receives new handle push offset DGROUP:oldout call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error ; open input file... push ds ; address of filename push offset DGROUP:iname push ds ; receives file handle push offset DGROUP:ihandle push ds ; receives DosOpen action push offset DGROUP:action push 0 ; file size (not used) push 0 push 0 ; attribute (not used) push 1 ; action: open if exists ; fail if doesn't push 40h ; access: read-only push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 jerr main1 ; exit if error ; create output file... push ds ; address of filename push offset DGROUP:oname push ds ; receives file handle push offset DGROUP:ohandle push ds ; receives DOSOPEN action push offset DGROUP:action push 0 ; initial file size push 0 push 0 ; attribute = normal push 12h ; action: create/replace push 41h ; access: write-only push 0 ; reserved DWORD 0 push 0 call DosOpen ; transfer to OS/2 jerr main1 ; exit if error ; make stdin track ; input file handle... push ihandle ; handle from DOSOPEN push ds ; standard input handle push offset DGROUP:newin call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error ; make stdout track ; output file handle... push ohandle ; handle from DOSOPEN push ds ; standard output handle push offset DGROUP:newout call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error ; run SORT.EXE as child... push ds ; receives failing dynlink push offset DGROUP:objbuff push objbuff_len ; length of buffer push 0 ; 0 = synchronous execution push 0 ; argument strings pointer push 0 push 0 ; environment pointer push 0 push ds ; receives PID, exit code push offset DGROUP:pcodes push ds ; child program pathname push offset DGROUP:pname call DosExecPgm ; transfer to OS/2 jerr main1 ; exit if error ; restore stdin handle ; to original meaning... push oldin ; dup of original stdin push ds ; standard input handle push offset DGROUP:newin call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error ; restore stdout handle ; to original meaning... push oldout ; dup of original stdout push ds ; standard output handle push offset DGROUP:newout call DosDupHandle ; transfer to OS/2 jerr main1 ; exit if error push oldin ; close dup of stdin call DosClose ; transfer to OS/2 jerr main1 ; exit if error push oldout ; close dup of stdout call DosClose ; transfer to OS/2 jerr main1 ; exit if error push ihandle ; close input file call DosClose ; transfer to OS/2 jerr main1 ; exit if error push ohandle ; close output file call DosClose ; transfer to OS/2 jerr main1 ; exit if error ; display success message... push stdout ; standard output handle push ds ; address of message push offset DGROUP:msg push msg_len ; length of message push ds ; receives bytes written push offset DGROUP:action call DosWrite ; transfer to OS/2 jerr main1 ; exit if error ; exit point if no errors push 1 ; terminate all threads push 0 ; exit code = 0 (success) call DosExit ; transfer to OS/2 main1: ; exit point if error push 1 ; terminate all threads push 1 ; exit code = 1 (error) call DosExit main endp _TEXT ends end main ; defines entry point Figure 16-7. Assembly language source code for EXECSORT.ASM, which demonstrates use of a filter as a child process. This program redirects the standard input and standard output handles to files, invokes DosExecPgm to run SORT.EXE as a child process, and then restores the original meaning of the standard input and standard output handles. To assemble the file EXECSORT.ASM into the file EXECSORT.OBJ, enter the following command: [C:\] MASM EXECSORT.ASM; To build the executable file EXECSORT.EXE from the files EXECSORT.OBJ, EXECSORT.DEF (Figure 16-8), and the import library OS2.LIB, enter the following command: [C:\] LINK EXECSORT,,,OS2,EXECSORT NAME EXECSORT NOTWINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 16-8. EXECSORT.DEF, the module definition for EXECSORT.EXE. Chapter 17 Device Drivers Device drivers are OS/2 modules that issue commands directly to peripheral devices and cause data to be transferred between those devices and RAM. They are thus the most hardware-dependent layer of OS/2. Drivers shield the operating system kernel from the need to deal with hardware I/O port addresses and variable characteristics (such as number of tracks per disk or sectors per track) of peripheral devices, in the same way that the kernel in turn shields application programs from the details of file and memory management. By convention, OS/2 device drivers reside in individual disk files that have the extension SYS. The essential or base driversthe keyboard, display, disk, printer, and clock driversare always loaded during the boot process. Other, optional device drivers can be loaded by DEVICE directives in the CONFIG.SYS file and are referred to as installable drivers. In reality, however, all OS/2 drivers have the same physical and logical structure and the same interface to the OS/2 kernel, and they must all be incorporated into the system at boot time. OS/2 device drivers are similar in many ways to both MS-DOS and XENIX/UNIX device drivers, but they have a number of unique capabilities and requirements. The experienced MS-DOS programmer, in particular, must be wary of terminology that sounds familiarthe associated driver function might be vastly different. A full-fledged OS/2 driver for any device is invariably more complex than its MS-DOS equivalent. Unique Aspects of OS/2 Drivers Six features of OS/2 device drivers have no counterpart under MS-DOS: The ability to handle more than one request at a time The availability of OS/2 kernel services and inter-driver communication Bimodal operation Support for IBM ROM BIOSequivalent services to real mode applications Support for device monitors The ability to "deinstall" on demand MS-DOS is a single-tasking operating system, and under normal circumstances, a maximum of one I/O operation can be in progress. Consequently, MS-DOS drivers are easy to write because the code need not be reentrant and polled I/O can be used whenever it is convenient. In OS/2, however, multiple processes can be active simultaneously, and each process can contain multiple threads that are issuing I/O requests independently. A device driver must, therefore, be reentrant and must be able to manage overlapping I/O requests for the same device. And to keep the cost in CPU cycles to a minimum, the driver must take full advantage of the interrupt and DMA capabilities of its device. OS/2's multitasking character does, however, yield some benefits for drivers. Under MS-DOS, device drivers must be self-contained. If, at the same time that it is carrying out an I/O request from the kernel, an MS-DOS driver requests a different operation from the kernel, the context of the original request is destroyed, and the system crashes. In contrast, most OS/2 kernel services are fully reentrant, and OS/2 can provide a battery of services to device drivers much as it does to application programs (although the nature of the services is, of course, much different). These services are called Device Driver Helpers, or DevHlp functions. In OS/2 version 1.1 drivers can also call each other directly through inter-driver communication (IDC) entry points. The term bimodal operation stems from OS/2's requirement that its device drivers be capable of servicing both I/O requests and hardware interrupts in either real mode or protected mode. Bimodality improves performance by reducing the number of mode switchesfor example, if the CPU is in real mode at the completion of an operation that was started in protected mode, the driver need not switch back to protected mode to service an interrupt. OS/2 assists the driver to achieve bimodal operation by providing it with special bimodal pointers and other services, which will be discussed later in the chapter. OS/2 drivers must contain ROM BIOS support because many MS-DOS applications call the ROM BIOS directly rather than performing all their I/O through documented MS-DOS serviceseither to obtain increased performance or to gain functionality that MS-DOS does not offer (EGA/VGA palette programming, for example). To support such applications in DOS compatibility mode, an OS/2 driver must capture the software interrupt vectors that are used to invoke ROM BIOS services for its device. It can then either interlock the ROM BIOS routines to prevent interference with the execution of protected mode applications, or substitute a new set of routines that provide equivalent services. Device monitors are an OS/2 innovation that allow you to write and install keyboard enhancers, macro-expanders, print spoolers, and similar utilities in a hardware-independent manner. An application program that wants to filter the data stream of a particular character device driver can register as a monitor for that device. Acting as the intermediary, the OS/2 kernel asks the driver whether it wants to accept the monitor registration. If the driver consents, it cooperates with the kernel to pass each character that it reads from or writes to the device through a buffer belonging to the monitor program, which can add, delete, or substitute characters at its discretion. Device monitors are discussed in detail in Chapter 18. Last, OS/2 driver support for deinstallation allows efficient use of memory and a more controlled environment than MS-DOS. Under MS-DOS, you can supersede a character device driver simply by installing another driver with the same logical device name; the first driver loaded has no way to prevent being superseded, and the memory it occupied is simply lost. Under OS/2, if a driver is loaded that has the same logical name as a previously loaded driver, the kernel asks the previous driver whether it is willing to be replaced. The first driver can agree to the request and release any interrupts and other system resources that it owns, after which its memory is reclaimed and the new driver is allowed to initialize itself. Alternatively, the first driver can refuse to deinstall, and the kernel then terminates installation of the new driver. Device Driver Types OS/2 device drivers are categorized into two groups: block device drivers and character device drivers. A driver's membership in one of these groups determines the way OS/2 views the associated device and the particular functions the driver itself must support. Character device drivers control peripheral devices, such as terminals or printers, that perform input and output operations one character (or byte) at a time. A particular character device driver ordinarily supports a single hardware unit, although a given SYS file can contain more than one character device driver. Each character device has a 1- to 8-character logical name; an application program can "open" the device by name for input or output (or both) as though it were a file. The logical name is strictly a means of identifying the driver to OS/2 and has no physical equivalent on the device. Block device drivers control peripheral devices that transfer data in chunks rather than byte by byte. Block devices are usually randomly addressable devices, such as disk drives, but they can also be sequential in nature, such as magnetic tape drives. A block driver can support more than one physical unit and can also map two or more logical units onto a single physical unit (such as a partitioned fixed disk). OS/2 assigns single-letter drive identifiers (A, B, and so forth) to block devices, instead of logical names. The letter assigned to a given block device is determined solely by the order in which the block drivers are loaded. The total number of letters assigned to a driver is determined by the number of logical units that the driver supports. Device Driver Modes Whenever an OS/2 device driver has control of the CPU, it is said to be executing in one of four different modes: kernel mode, interrupt mode, user mode, or init mode. Understanding these modes and their implications is vital to writing an OS/2 device driver because the current mode determines the kernel services (DevHlps) available to a driver and the actions that the driver can take. A driver is in kernel mode when the kernel calls the driver's strategy entry point (explained later) to obtain information or to start an I/O operation, usually as the immediate result of an API call by an application. In kernel mode, the driver executes in ring 0 with nearly every DevHlp service available to it. A driver executes in interrupt mode as the result of a hardware interrupt. The interrupt is initially fielded by the kernel, which saves all registers, sets DS to point to the driver's data segment, and then transfers control to the driver's interrupt handler. In interrupt mode, the driver also executes in ring 0 but has only a subset of DevHlp services available. The driver executes in user mode when it is entered as a result of a software interrupt by an application executing in the DOS compatibility environment. The only drivers that must concern themselves with this mode are those that must provide IBM ROM BIOSequivalent services in a manner consistent with the multitasking and memory protection requirements of OS/2. Because a driver runs in user mode only when the CPU is in real mode, the concept of privilege levels is irrelevant, but you can think of the driver as executing in ring 0 because no protection mechanisms prevail. Relatively few DevHlp services are available to the driver in user mode. A driver runs in init mode only oncewhen the kernel calls the driver to initialize itself immediately after the driver is loaded during the boot process. In init mode, the driver runs as though it were an application program with I/O privilege (IOPL). A limited set of dynlink API calls, as well as a subset of the DevHlp services, are available to a driver in init mode. Two additional terms that pertain to a driver's execution mode are task time and interrupt time. A driver is executing at task time when it is called synchronouslyas the direct result of an application API request or a real mode software interrupt; this context would include both the user mode and kernel mode, as described above. Interrupt time refers to the asynchronous invocation of a driver routineas the result of an external hardware event. At interrupt time, the current CPU mode, process, and LDT are unpredictable. Device Driver Structure An OS/2 device driver has three major components: the device driver header, the strategy routine, and the interrupt routine. The Device Driver Header The device driver header always appears at the start of the device driver's near data segment. The header contains information about the driver that the OS/2 kernel can inspect when it needs to satisfy an application program's I/O request (Figure 17-1 on the following page). The first element of the header is a pointer to the next driver in the system's chain of all device drivers. This pointer is initialized to -1, -1; the system fills in the true value when the driver is loaded. If you include multiple drivers and headers in a single file, link the headers together and initialize only the last header's link field to -1, -1. 00H Ŀ Link to next driver header, offset 02H Ĵ Link to next driver header, selector 04H Ĵ Device attribute word 06H Ĵ Offset, strategy entry point 08H Ĵ Offset, IDC entry point 0AH Ĵ Name 12H Ĵ Reserved 1AH Figure 17-1. OS/2 device driver header. In character device drivers, the Name field contains the logical name of the device padded with spaces if necessary to a length of 8 bytes. In block device drivers, the first byte of the Name field contains the number of logical units supported by the driver (filled in by OS/2); the remaining 7 bytes of the name field are not used. The other important components of the header are a word (16 bits) of device driver attribute flags, the offsets of the driver's strategy routine and IDC entry point, and the logical device name (for a character device such as PRN or COM1) or the number of logical units (for a block device). When present, the device name must be left-justified, all uppercase, and padded with spaces to a length of eight characters. The device attribute word in the header (Figure 17-2) defines the driver's function level and indicates whether it controls a character or a block device, supports certain optional driver functions, and reads or writes IBM-compatible disk media. The least significant four bits determine whether OS/2 should use that driver as the standard input, standard output, clock, or NUL device; as you might expect, each of these four bits should be set on only one driver at a time. Examples of source code for device driver headers appear in Figures 17-3, 17-4, and 17-5, which are all on p. 354. F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ 1 = standard input 1 = standard output 1 = NUL device 1 = clock device Reserved Function level 001 for OS/2 1.0 and 1.1 Reserved 0 = Open/Close/RM not supported 1 = Open/Close/RM supported 0 = sharper ignores competing opens 1 = sharper controls device contention (character devices only) 0 = IBM-compatible (FAT ID byte valid) 1 = non-IBM-compatible disk format 0 = inter-driver communication (IDC) not supported 1 = IDC supported 0 = block device 1 = character device Figure 17-2. The device attribute word in the device driver header, which describes the characteristics and capabilities of the associated driver. header dd -1 ; link to next driver dw 8880h ; device attribute word ; bit 15 = 1 character device ; bit 11 = 1 Open/Close/RM ; bits 7-9 = 001 driver level dw Strat ; strategy entry point dw 0 ; IDC entry point db 'COM1 ' ; logical device name db 8 dup (0) ; (reserved) Figure 17-3. Source code for an OS/2 character device driver header. header dd -1 ; link to next driver dw 0880h ; device attribute word ; bit 15 = 0 block device ; bit 11 = 1 Open/Close/RM ; bits 7-9 = 001 driver level dw Strat ; strategy entry point dw 0 ; (reserved) db 0 ; units (filled in by OS/2) db 15 dup (0) ; (reserved) Figure 17-4. Source code for an OS/2 block device driver header. ; header for first device... head1 dd head2 ; link to next driver dw 8880h ; device attribute word dw Strat1 ; strategy entry point dw 0 ; IDC entry point db 'COM1 ' ; logical device name db 8 dup (0) ; (reserved) ; header for second device... head2 dd -1 ; link to next driver dw 8880h ; device attribute word dw Strat2 ; strategy entry point dw 0 ; IDC entry point db 'COM2 ' ; logical device name db 8 dup (0) ; (reserved) Figure 17-5. Source code demonstrating the linkage of two device headers in the same driver file. The Strategy Routine The OS/2 kernel initiates an I/O operation by calling the driver's strategy routine (whose offset is in the device driver header) with the address of a data structure called a request packet in registers ES:BX. The first 13 bytes of the request packet have a fixed format and are known as the static portion; they are followed by data that varies according to the operation being requested (Figure 17-6). To support multiple concurrent I/O requests, the driver maintains a private queue of request packets for its pending operations with the aid of the kernel DevHlp functions. Note that the system passes the request packet address in the form of a bimodal pointer. In a bimodal pointer, the protected mode selector is identical to the corresponding real mode segment so the same address is valid in either real mode or protected mode. As a general rule, OS/2 can generate bimodal pointers only for data structures that it creates or owns (such as request packets) because the data must be located in the first 640 KB of memory. 00H Ŀ Length of request packet 01H Ĵ Block device unit number 02H Ĵ Command code (driver subfunction) 03H Ĵ Status word (returned to kernel) 05H Ĵ Reserved area 09H Ĵ Queue linkage 0DH Ĵ Command-specific data (length varies) Figure 17-6. A prototypal OS/2 device driver request packet. The OS/2 kernel requests a driver operation by passing the bimodal address of a request packet containing operation-specific parameters, and the driver communicates the results of the operation to the kernel by placing the status and other information in the same packet. The crucial fields of the request packet (in the static portion) are the command code, which selects a particular driver function, and the status word (Figure 17-7), which is the primary means by which the driver returns information to the kernel. The driver can carry out many functions immediatelyat the time of the strategy call; in such cases, it simply places any necessary information into the request packet, sets the Done bit in the status word, and returns to the kernel. If it encounters an error, the driver also sets the Error bit in the status word, along with the appropriate code for the error type (Figure 17-8). However, some driver functions, such as Read and Write, involve an unpredictable amount of delay and cannot be carried out during the strategy call without interfering with multitasking; they must be completed with the aid of the interrupt routine (discussed below). If the device is idle, the strategy routine starts the I/O operation and then returns control to the kernel so that other work in the system can proceed. If the device is busy, the new request packet is instead chained onto the driver's request queue or work list. Incidentally, a block device driver is expected to sort its work list in such a way that the average transfer time is minimized. (A kernel DevHlp is available to perform this sorting.) F E D C B A 9 8 7 6 5 4 3 2 1 0 Ŀ Error code, if bit 15 = 1 0 = not done 1 = done 0 = not busy 1 = busy Reserved (0) 0 = normal error codes 1 = error codes are driver-specific 0 = no error 1 = error Figure 17-7. The request packet status word returned to the kernel by the driver after an operation is completed. Error Code Description 00H Write-protect violation 01H Unknown unit 02H Device not ready 03H Unknown command 04H CRC error 05H Bad drive request structure length 06H Seek error 07H Unknown medium 08H Sector not found 09H Printer out of paper 0AH Write fault 0BH Read fault 0CH General failure 0DH Change disk 0EH Reserved 0FH Reserved 10H Uncertain medium 11H Character I/O call interrupted 12H Monitors not supported 13H Invalid parameter Figure 17-8. The error codes used in the request packet status word. Error 0DH can be returned by drivers that map more than one logical unit onto the same physical drive. It causes the kernel to prompt the user to switch disks. A driver should return error 10H (Uncertain medium) when a drive-not-ready condition is detected, when accessing removable media without change-line support and a delay of more than two seconds occurs between accesses, or when accessing a drive with change-line support and the change-line indicates that the disk might have been replaced. The driver should continue to return error 10H for all requests until it receives a Reset Media request packet (command code 17). The Interrupt Routine The interrupt routine is entered from the kernel as a result of a hardware interrupt from the physical device, when an I/O operation either has been completed or has been aborted because of a hardware error. Because hardware interrupts are by nature asynchronous and unpredictable, an OS/2 device driver must be capable of servicing an interrupt properly in either protected mode or real mode. The interrupt routine queries the peripheral controller to determine the outcome of the I/O operation, sets the appropriate status and error information in the request packet, calls the DevHlp routine DevDone to signal the kernel that the I/O is complete, and removes the request packet from its request queue. The interrupt routine then examines the request queue to determine whether additional requests are pending for the device. If the request queue is not empty, the interrupt routine removes the request packet from the head of the queue and starts the next I/O operation. It then clears the Carry flag and exits to the kernel with a far return. If multiple devices (and their drivers) share the same interrupt level, the kernel calls each driver's interrupt routine in turn until one indicates its acceptance of the interrupt by clearing the Carry flag. The other interrupt routines must return with the Carry flag set. The Command Code Functions A total of 27 command codes are defined for OS/2 device drivers, some of which are reserved for future expansion or for backward compatibility with MS-DOS drivers. The command codes and the names of their associated functions are shown in Figure 17-9. Some command codes are relevant only for character drivers and some only for block device drivers; a few are used in both. Whichever type of driver you are writing, you need to supply an executable routine for each command code, even if the routine does nothing but set the Done bit in the request packet status word. The following pages describe each of the command codes in detail. Command Hex Function Name Character Block Code Value Devices Devices 0 00H Init (Initialization) C B 1 01H Media Check B 2 02H Build BPB B 3 03H Reserved 4 04H Read (Input) C B 5 05H Nondestructive Read C 6 06H Input Status C 7 07H Flush Input Buffers C 8 08H Write (Output) C B 9 09H Write with Verify C B 10 0AH Output Status C 11 0BH Flush Output Buffers C 12 0CH Reserved 13 0DH Device Open C B 14 0EH Device Close C B 15 0FH Removable Media B 16 10H Generic IOCtl C B 17 11H Reset Media B 18 12H Get Logical Drive Map B 19 13H Set Logical Drive Map B 20 14H DeInstall Driver C 21 15H Reserved 22 16H Partitionable Fixed B Disk 23 17H Get Fixed Disk Map B 24 18H Reserved 25 19H Reserved 26 1AH Reserved Figure 17-9. The strategy routine functions selected by the command code field of the request packet. The Init Function The Init (Initialization) routine (command code 0) for a driver is called only oncewhen the driver is loaded. It must ensure that the peripheral device controlled by the driver is present and functional, perform any necessary hardware initialization (such as a reset on a printer), register with the system for any interrupts that the driver will need later, and initialize driver data structures (such as semaphores or the request queue). The parameters and results for this command code are summarized in Figure 17-10 on the following page. The kernel passes two addresses to the driver in the Init request packet: a bimodal pointer to the DevHlp common entry point (discussed in detail later) and a pointer to the variable part of the DEVICE= line (from the CONFIG.SYS file) that caused the driver to be loaded. The line is read-only and is terminated by a null (zero) byte; the driver can scan it for switches or other parameters that may influence its operation. For example, if the driver TINYDISK.SYS is loaded with the following line in the CONFIG.SYS file: DEVICE=tinydisk.sys 1024K the request packet contains a pointer to the following sequence of bytes: 74 69 6E 79 64 69 73 6B 2E 73 79 73 20 31 30 32 34 4B 00 Block drivers are also passed the drive code that will be assigned to their first logical unit (0 = A, 1 = B, and so forth). Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH ͻ Logical units 0EH Ĵ 0EH ͼ Length of code segment DevHlp entry point 10H Ĵ Length of data segment 12H Ĵ 12H ͻ Pointer to Address of BPB INIT arguments pointer array 16H ͻ 16H ͼ ͻ Block First unit First unit ͼ devices 17H ͼ 17H only Figure 17-10. Request packet format for the Init function (command code 0). The Init function must return the status and the amount of memory that it wants to reserve for the driver's code and data segments. This allocation allows the driver to discard initialization code and data (thus minimizing its memory requirements) by positioning these items at the end of their respective segments. A block device driver must also return the number of logical units it supports and the address of a BPB pointer array. The number of units returned by a block driver is used to assign device identifiers. Suppose, for example, drivers are already present for four block devices (drive codes 0 through 3, corresponding to drive identifiers A through D). If a new driver is initialized that supports four units, it will be assigned the drive numbers 4 through 7 (corresponding to the drive names E through H). Although the device driver header also contains a field for the number of units, OS/2 does not inspect that field during initialization; instead, the system sets that header field using information returned by the Init function. The BPB pointer array is an array of word offsets to BIOS parameter blocks (Figure 17-11). The array must contain one entry for each logical unit that the driver supports, although all entries can point to the same BPB to conserve memory. During the boot sequence, OS/2 scans the BPBs of every block device to determine the largest sector size used in the system; it then uses this information to set the size of the buffers in the system's disk cache. Offset Length Contents 00H 2 Bytes per sector 02H 1 Sectors per allocation unit (cluster), always a power of two 03H 2 Reserved sectors (starting at logical sector 0) 05H 1 Number of file allocation tables 06H 2 Number of root directory entries 08H 2 Number of sectors in logical volume (including boot sector, FAT, etc.) 0AH 1 Medium descriptor byte 0BH 2 Number of sectors per FAT 0DH 2 Sectors per track 0FH 2 Number of heads 11H 4 Number of hidden sectors before reserved sectors 15H 4 Total sectors in logical volume (if word at offset 08H = 0) 19H 6 Reserved Figure 17-11. Structure of BIOS parameter block (BPB). If the Init routine finds that the driver's hardware device is missing or defective, it can cancel driver installation by returning the following values in the request packet: zero for the number of units, zero for the code and data segment lengths, and a status of 810CH (Error flag, Done flag, and the error code for a general failure). If the driver's data segment (which has a maximum size of 64 KB) is not large enough, the Init routine can call the DevHlp function AllocPhys to reserve additional memory. This additional memory can be either above or below the 1 MB boundary and is not movable or swappable. The driver itself is always loaded in the first 640 KB of memory. When the driver is called in real mode, it has faster access to memory allocated below 1 MB than to memory above that boundary, but allocating low memory decreases the amount of space available for a program running in DOS compatibility mode. In general, a driver should keep data items to which it needs frequent and rapid access in its near data segment or in additional memory allocated below 1 MB, and it should put large or infrequently used data structures and buffers above 1 MB. Unlike the other command code routines, which run in kernel mode, the Init routine runs as though it were an application with I/O privilege (IOPL). Consequently (and also unlike all other command code routines) the Init routine can invoke certain file access and internationalization API functions, which are listed in Figure 17-12. These assist the driver in finding and loading font files or other device-dependent information and in displaying error or status messages. When running on an IBM PS/2 or compatible, the Init routine can also call ABIOS services. Function Description DosBeep Generates tone DosCaseMap Translates ASCII string in place DosChgFilePtr Moves file read/write pointer DosClose Closes file or device DosDelete Deletes file DosDevConfig Returns system hardware configuration DosDevIOCtl Device-specific commands and information DosFindClose Releases directory search handle DosFindFirst Searches for first matching file DosFindNext Searches for next matching file DosGetCtryInfo Returns internationalization information DosGetDBCSEv Returns DBCS environment vector DosGetEnv Returns pointer to environment DosGetMessage Returns text for system message DosOpen Opens file or device DosPutMessage Sends message to file or device DosQCurDir Returns current directory DosQCurDisk Returns current disk drive DosQFileInfo Returns file information DosQFileMode Returns file attributes DosRead Reads from file or device DosWrite Writes to file or device Figure 17-12. API calls that can be called during driver initialization (command code 0). The Media Check Function The Media Check function (command code 1) is used in block device drivers only. The parameters with which the driver is called and the results that the function returns are summarized in Figure 17-13. The OS/2 kernel calls the Media Check function before it services a drive access call other than a simple file read or writesuch as a file open, close, rename, or delete. It passes the medium ID byte (Figure 17-14 on the following page) for the disk that OS/2 assumes is in the drive. The driver returns a code indicating whether the medium has been changed since the last read or write. This change code has one of the following values: Code Meaning -1 Medium has been changed 0 Don't know if medium has been changed 1 Medium has not been changed Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ Unit 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Media descriptor 0EH Ĵ 0EH Ĵ Change code 0FH Ĵ 0FH Ĵ Pointer to previous volume name 13H 13H Figure 17-13. Request packet format for the Media Check function (command code 1). If the disk has not been changed, OS/2 accesses the disk without first rereading its FAT. Otherwise, OS/2 calls the driver's Build BPB function (see below) and then reads the disk's FAT and directory. If the Open/Close/RM flag (bit 11) is set in the attribute word of the device driver header, the Media Check function must also return a pointer to the ASCIIZ volume label for the previous disk in the drive (regardless of the change code). If the driver does not have the volume label, it can return a pointer to the ASCIIZ string NO NAME. This allows OS/2 to prompt the user to insert the correct disk if the disk has been changed and the system's buffers hold data that has not been written out. Value Disk Type(s) 0F0H 3.5", 2-sided, 18 sectors 0F8H Fixed disk 0F9H 3.5", 2-sided, 9 sectors 5.25", 2-sided, 15 sectors 0FCH 5.25", 1-sided, 9 sectors 0FDH 5.25", 2-sided, 9 sectors 8", 1-sided, single-density 0FEH 5.25", 1-sided, 8 sectors 8", 1-sided, single-density 8", 2-sided, double-density 0FFH 5.25", 2-sided, 8 sectors Figure 17-14. Medium descriptor bytes for IBM-compatible disks. (The table includes 8-inch disk format codes for historical reasons only.) The Build BPB Function The Build BPB function (command code 2) is defined for block devices only. OS/2 calls this function after a Media Check request returns the Medium changed or Don't know codes. The parameters with which the driver is called and the results it returns are summarized in Figure 17-15. The Build BPB routine receives a pointer in the request packet to a 1-sector buffer. If bit 13 (the Non-IBM-Format bit) in the attribute word of the device driver header is clear, the buffer contains the first sector of the disk's FAT, with the medium ID byte in the first byte of the buffer. If bit 13 is set, the buffer contains the boot sector from the disk (logical sector 0). The driver should not modify the buffer contents. The Build BPB function must return a status and a pointer to a BIOS parameter block (Figure 17-11 on p. 361) for the disk format indicated by the medium ID byte. The kernel uses the information in the BPB to interpret the disk structure, and the driver itself uses the BPB to translate logical sector addresses into physical track, sector, and head addresses. The Build BPB function should also read the volume label off the disk and save it. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ Unit 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Medium descriptor 0EH Ĵ 0EH Ĵ Pointer to FAT buffer 12H Ĵ 12H Ĵ Pointer to BPB 16H 16H Figure 17-15. Request packet format for the Build BPB function (command code 2). The Read, Write, and Write with Verify Functions The Read function (command code 4) transfers data from the device to the specified memory address. The Write function (command code 8) transfers data from the specified memory address to the device. The Write with Verify function (command code 9) transfers data from the specified memory address to the device and then verifies that the data was transferred correctly (preferably by reading the data back and comparing it to the original data). The OS/2 kernel calls Write with Verify, instead of Write, whenever the system's global Verify flag is enabled with the VERIFY command or with the API function DosSetVerify. The parameters and results for these command codes are summarized in Figure 17-16 on the following page. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H ͻ 01H Ĵ Unit 02H ͼ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH ͻ 0DH Ĵ Media descriptor 0EH ͼ 0EH Ĵ Transfer address 12H Ĵ 12H Ĵ Bytes/sectors requested Bytes/sectors transferred 14H ͻ 14H Ĵ Starting sector number 18H ͼ 18H Ĵ Reserved 1AH 1AH ͻ ͼ Block devices only Figure 17-16. Request packet format for the Read, Write, and Write with Verify functions (command codes 4, 8, and 9). Transfer address is locked and converted to a 32-bit physical address by kernel. The OS/2 kernel calls all three of these driver functions with the memory address and length of the data to be transferred. The memory address is a locked 32-bit physical address; that is, the memory is not swappable or movable, and the segment:offset or selector:offset pair has been converted to a number in the range 00000000H through 00FFFFFFH. (Physical addresses treat the 16 MB of physical RAM as a linear array of bytes.) The driver can use DevHlp functions, which are described later, to convert the physical address into a far pointer appropriate for the current CPU mode. In the case of block device drivers, the kernel also passes the drive unit code, the starting logical sector number, and the medium ID byte for the disk. The driver uses the drive's BPB to translate the logical sector number into a physical track, head, and sector. When the Read, Write, or Write with Verify operation is complete, the functions must return (or the interrupt routine must return on their behalf) a status and the number of bytes or sectors actually transferred. If an I/O error occurs, the Done flag, Error flag, error type, and the number of bytes or sectors successfully transferred prior to the error must be returned to the kernel. The kernel then unlocks the memory. The Nondestructive Read Function The Nondestructive Read function (command code 5) is meaningful in DOS compatibility mode and for character devices only. It returns the next character in the driver's internal buffer, without removing that waiting character from the buffer. Its principal use is to let OS/2 check for a Ctrl-C entered at the keyboard. The parameters and results for this command code are summarized in Figure 17-17. The function returns its result in the Busy bit of the status word. If the driver's input buffer is empty, the function should set the Busy bit. If at least one character is waiting in the input buffer, the function should clear the Busy bit and return the character that would be obtained by a call to the driver's Read function, without removing that character from the buffer. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Character 0EH 0EH Figure 17-17. Request packet format for Nondestructive Read (command code 5). The Input Status and Output Status Functions The Input Status and Output Status (command codes 6 and 10) functions are defined only for character devices. Both return their results in the Busy bit of the status word. The parameters and results for these command codes are summarized in Figure 17-18. OS/2 calls the Input Status function to determine whether characters are waiting, buffered within the driver. The function clears the Busy bit if at least one character is already in the driver's input buffer. It sets the Busy bit if no characters are in the buffer, in which event a Read request for one character would not complete immediately. If the driver does not buffer characters internally, the Input Status function should always clear the Busy bit so that OS/2 will not wait for a character arrive in the buffer before issuing a Read request. OS/2 uses the Output Status function to determine whether a write operation is already in progress for the device. The function clears the Busy bit if the device is idle and if a Write request would start immediately, or it sets the Busy bit if a write is already in progress. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH 0DH Figure 17-18. Request packet format for the Input Status (command code 6), Flush Input Buffers (command code 7), Output Status (command code 10), and Flush Output Buffers (command code 11) functions. The Flush Input Buffer and Flush Output Buffer Functions The Flush Input Buffer (command code 7) and Flush Output Buffer (command code 11) functions are supported only for character device drivers. They simply terminate any read (for Flush Input) or write (for Flush Output) operations that are in progress, discard any requests that are pending on the driver's work list, and empty the associated buffer. The parameters and results for these command codes are summarized in Figure 17-18. These functions constitute the driver level support for DosDevIOCtl Category 11 Functions 01H (Flush Input Buffer) and 02H (Flush Output Buffer). The Device Open and Device Close Functions The Device Open and Device Close (command codes 13 and 14) functions are called only if the Open/Close/Removable Media flag (bit 11) is set in the attribute word of the device driver header. If a Device Open or Device Close request is directed to a block device driver, the request packet includes a unit code. The parameters and results for these command codes are summarized in Figure 17-19. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H ͻ 01H Ĵ Unit 02H ͼ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Reserved 0FH 0FH ͻ Block devices only ͼ Figure 17-19. Request packet format for the Device Open (command code 13) and Device Close (command code 14) functions. Each application call to DosOpen to open or create a file or to open a character device for input or output results in a Device Open request from the kernel to the corresponding device driver. Similarly, each DosClose call by an application to close a file or device results in a Device Close call by the kernel to the appropriate driver. On block devices, you can use the Device Open and Device Close functions to manage local buffering and to maintain a reference count of the number of open files on a device. Whenever this reference count is decremented to zero, indicating that all files on the disk have been closed, the driver should flush any internal buffers so that no data is lost if the user proceeds to change disks. Character device drivers can respond to the Device Open and Device Close by sending hardware-dependent initialization and post-I/O strings to the associated device (for example, a reset sequence or formfeed character to precede new output, and a formfeed to follow it). If the driver is appropriately designed, an application can inspect or change the strings with DosDevIOCtl calls. When an application issues a call to DosMonOpen or DosMonClose, the driver receives a special Device Open or Device Close request packet with bit 3 of the status word set. These functions assist the driver in maintaining its device monitor chain, as described in more detail in Chapter 18. The Removable Media Function The Removable Media function (command code 15) is defined for block devices only. OS/2 does not call this function unless the Open/Close/ Removable Media flag (bit 11) is set in the attribute word of the device driver header. This function constitutes the driver level support for the service that OS/2 provides to application programs with DosDevIOCtl Category 8 Function 20H (Check if Block Device Removable). The parameters and results for this command code are summarized in Figure 17-20. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H ͻ 01H Ĵ Unit 02H ͼ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH 0DH ͻ Block devices only ͼ Figure 17-20. Request packet format for the Removable Media (command code 15), Reset Media (command code 17), and DeInstall Driver (command code 20) functions. The only parameter for the Removable Media function is the unit code. The function returns its result in the Busy bit of the status word. The Busy bit is set if the disk is fixed; it is cleared if the disk is removable. The Generic IOCtl Function The Generic IOCtl function (command code 16) corresponds to the OS/2 API function DosDevIOCtl; it provides a general-purpose high performance channel of direct communication between applications and device drivers. The parameters and results for this command code are summarized in Figure 17-21 on the following page. In addition to the usual information in the static portion of the request packet, the Generic IOCtl function is passed a category (major) code, a function (minor) code, and pointers to a parameter block and a data buffer. The pointers are virtual addresses; the driver is responsible for ensuring that the application is actually authorized to use those virtual addresses. If the request cannot be completed at task time, your driver must ensure later addressability of the parameter block and data buffer by accomplishing the following sequence of steps: 1. Lock the virtual addresses so that the corresponding memory segments will not be moved or swapped by the system's memory manager. 2. Replace the virtual addresses in the request packet with the corresponding physical addresses. 3. When the driver is able to service the request, translate the physical addresses back to virtual addresses that are appropriate for the current mode. 4. When the request has been serviced, unlock the segments. The driver must interpret the category and function codes in the request packet and the contents of the parameter block or data buffer (or both) to determine which operation it will carry out; subsequently, it sets the status word appropriately and returns any other pertinent information in the application's data buffer. Services that can be invoked by the Generic IOCtl function, if the driver supports them, include configuring a serial port, selecting nonstandard disk formats, reading and writing entire tracks, and formatting and verifying tracks. The Generic IOCtl function is designed to allow easy communication between closely coupled applications and custom device drivers, and is open-ended so that it can be used to extend the device driver definition in future versions of OS/2. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H ͻ 01H Ĵ Unit 02H ͼ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Category code 0EH Ĵ 0EH Ĵ Function code OFH Ĵ OFH Ĵ Pointer to parameter block 13H Ĵ 13H Ĵ Pointer to data buffer 17H Ĵ 17H Ĵ Reserved 19H 19H ͻ Block devices only ͼ Figure 17-21. Request packet format for the Generic IOCtl function (command code 16). Both pointers are virtual addresses, with selectors from the current process's LDT. The process's right to use the addresses must be confirmed with the DevHlp function VerifyAccess, and the virtual addresses must be locked and converted to physical addresses if request cannot be disposed of at task time. The Reset Media Function The Reset Media function (command code 17) is defined only for block devices. The function is called with no parameters in the request packet other than the command and unit codes, and it returns only a status to the kernel. The parameters and results for this command code are summarized in Figure 17-20 on p. 370. When the driver returns an Uncertain medium error to a service request, the kernel calls Reset Media to inform the driver that it no longer needs to return that error for the drive. The Get Logical Drive and Set Logical Drive Functions The Get Logical Drive and Set Logical Drive functions (command codes 18 and 19) are defined for block devices only. They correspond to the API services supplied by OS/2 to application programs via DosDevIOCtl Category 8, Functions 03H (Set Logical Map) and 21H (Get Logical Map). Both functions are called with a logical unit code in the request packet and must return a status and a logical unit. The parameters and results for these command codes are summarized in Figure 17-22. The Get Logical Drive function is called to determine whether more than one logical unit code is assigned to the same physical device. It returns a code for the last drive letter used to reference the device (1 = A, 2 = B, and so on); if only one drive letter is assigned to the device, the returned unit code should be zero. The Set Logical Device function is called to inform the driver of the next logical drive code that will be used to reference the device. The unit code passed by the OS/2 kernel is zero-based relative to the logical drives supported by this particular driver. The driver performs the requested mapping and returns the logical unit unchanged. For example, if the driver supports two logical floppy disk units (A and B), and only one physical floppy disk drive exists in the system, then a call to Set Logical Device with a unit number of 1 informs the driver that the next read or write request from OS/2 will be directed to logical drive B. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ Unit Unit 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH 0DH Figure 17-22. Request packet format for the Get Logical Drive and Set Logical Drive functions (command codes 18 and 19). The DeInstall Driver Function The DeInstall Driver function (command code 20) is valid for character devices only. It is called when the OS/2 kernel loads another character device driver with the same logical name as the driver that receives the DeInstall Driver request packet. The parameters and results for this command code are summarized in Figure 17-20 on p. 370. If the current driver refuses the DeInstall Driver command, it should return to the kernel with the status word set to 8103H (Error bit, Done bit, and error code unknown command). In this event, the system does not load the replacement driver. If the current driver accepts the DeInstall Driver command, allowing itself to be superseded, it must perform the following actions: Call the DevHlp function FreePhys to release any memory it has previously allocated with AllocPhys Call the DevHlp function UnSetIRQ to release any interrupts it has previously registered with SetIRQ Relinquish any device logical IDs it has previously acquired by calling the DevHlp function FreeLIDEntry (if the driver runs on an IBM PS/2 and uses the ABIOS) Perform any other necessary cleanup of system resources, such as semaphores Set the Done flag in the status word of the request packet After the driver accepting the DeInstall Driver command returns to the kernel, its code and memory segments are released. If the physical device supported by the driver cannot be told to stop generating interrupts, then the driver must refuse the DeInstall Driver operation rather than risk leaving the system without an interrupt handler. The Partitionable Fixed Disks Function The Partitionable Fixed Disks function (command code 22) is defined for block device drivers only. The function returns a status along with the number of physical, partitionable fixed disks that the driver supports. The parameters and results for this command code are shown in Figure 17-23. The information returned by this function allows the kernel to route the DosDevIOCtl Category 9 (Physical Disk Control) requests to the appropriate block device driver. Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ Unit 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Count 0EH Ĵ 0EH Ĵ Reserved 10H Ĵ 10H Ĵ Reserved 12H 12H Figure 17-23. Request packet format for the Partitionable Fixed Disks function (command code 22). The Get Fixed Disk/Logical Unit Map Function The Get Fixed Disk/Logical Unit Map function (command code 23) is defined for block device drivers only. The function is called with the unit number for a physical fixed disk in the request header; this number is calculated on the basis of previous calls to Partitionable Fixed Disks (command code 22). The parameters and results for this command code are summarized in Figure 17-24 on the following page. The device driver is responsible for returning a status and a 4-byte "units-supported bit mask" that describes which of its own logical units, numbered from zero, exist on the specified physical drive. The bits in the mask are assigned sequentially from the least significant bit of the first byte (the byte at the lowest address) to the most significant bit of the last byte (the byte at the highest address). Driver called with... Driver returns... 00H Ŀ 00H Ŀ Length 01H Ĵ 01H Ĵ Unit 02H Ĵ 02H Ĵ Command code 03H Ĵ 03H Ĵ Status 05H Ĵ 05H Ĵ Reserved 09H Ĵ 09H Ĵ Queue linkage 0DH Ĵ 0DH Ĵ Units-supported bit mask 11H Ĵ 11H Ĵ Reserved 13H Ĵ 13H Ĵ Reserved 15H 15H Figure 17-24. Request packet format for the Get Fixed Disk/Logical Unit Map function (command code 23). A Skeleton Device Driver The listing TEMPLATE.ASM (Figure 17-25) provides the source code for a skeleton character device driver called TEMPLATE.SYS. Its module definition file is TEMPLATE.DEF (Figure 17-26 on p. 383). This driver contains all the essential elements of an OS/2 device driver in rudimentary form: the device header, strategy routine, interrupt routine, and all the command code functions. It also demonstrates the preferred segment ordering and naming conventions, the use of API calls at initialization time, and the method by which initialization code and data can be discarded to minimize a driver's memory requirements. TEMPLATE.SYS performs no useful function in its present form; it simply returns a success code for all request packets it receives from the kernel. It is intended only to serve as a starting point for your own device drivers. title TEMPLATE -- Sample Device Driver page 55,132 .286 ; ; TEMPLATE.ASM ; ; A sample OS/2 character device driver. The driver command code ; routines are stubs only and have no effect but to return a ; nonerror "done" status. ; ; Assemble with: C> masm template.asm; ; Link with: C> link template,template.sys,,os2,template ; ; To install the driver, add "DEVICE=TEMPLATE.SYS" to CONFIG.SYS ; and reboot. ; ; Copyright (C) 1988 Ray Duncan ; maxcmd equ 26 ; maximum allowed command code stdin equ 0 ; standard device handles stdout equ 1 stderr equ 2 cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed extrn DosWrite:far DGROUP group _DATA _DATA segment word public 'DATA' ; device driver header... header dd -1 ; link to next device driver dw 8880h ; device attribute word dw Strat ; Strategy entry point dw 0 ; IDC entry point db 'TEMPLATE' ; logical device name db 8 dup (0) ; reserved devhlp dd ? ; DevHlp entry point wlen dw ? ; receives DosWrite length ; Strategy routine dispatch table ; for request packet command code... dispch dw Init ; 0 = initialize driver dw MediaChk ; 1 = media check dw BuildBPB ; 2 = build BIOS parameter block dw Error ; 3 = not used dw Read ; 4 = read from device dw NdRead ; 5 = nondestructive read dw InpStat ; 6 = return input status dw InpFlush ; 7 = flush device input buffers dw Write ; 8 = write to device dw WriteVfy ; 9 = write with verify dw OutStat ; 10 = return output status dw OutFlush ; 11 = flush output buffers dw Error ; 12 = not used dw DevOpen ; 13 = device open dw DevClose ; 14 = device close dw RemMedia ; 15 = removable media dw GenIOCTL ; 16 = generic IOCTL dw ResetMed ; 17 = reset media dw GetLogDrv ; 18 = get logical drive dw SetLogDrv ; 19 = set logical drive dw DeInstall ; 20 = deinstall dw Error ; 21 = not used dw PartFD ; 22 = partitionable fixed disks dw FDMap ; 23 = get fixed disk unit map dw Error ; 24 = not used dw Error ; 25 = not used dw Error ; 26 = not used ident db cr,lf,lf db 'TEMPLATE Sample OS/2 Device Driver' db cr,lf ident_len equ $-ident _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP,es:NOTHING Strat proc far ; Strategy entry point ; ES:BX = request packet address mov di,es:[bx+2] ; get command code from packet and di,0ffh cmp di,maxcmd ; supported by this driver? jle Strat1 ; jump if command code OK call Error ; bad command code jmp Strat2 Strat1: add di,di ; branch to command code routine call word ptr [di+dispch] Strat2: mov es:[bx+3],ax ; status into request packet ret ; back to OS/2 kernel Strat endp Intr proc far ; driver Interrupt handler clc ; signal we owned interrupt ret ; return from interrupt Intr endp ; Command code routines are called by the Strategy routine ; via the Dispatch table with ES:BX pointing to the request ; header. Each routine should return ES:BX unchanged ; and AX = status to be placed in request packet: ; 0100H if 'done' and no error ; 0000H if thread should block pending interrupt ; 81xxH if 'done' and error detected (xx=error code) MediaChk proc near ; function 1 = media check mov ax,0100h ; return 'done' status ret MediaChk endp BuildBPB proc near ; function 2 = build BPB mov ax,0100h ; return 'done' status ret BuildBPB endp Read proc near ; function 4 = read mov ax,0100h ; return 'done' status ret Read endp NdRead proc near ; function 5 = nondestructive read mov ax,0100h ; return 'done' status ret NdRead endp InpStat proc near ; function 6 = input status mov ax,0100h ; return 'done' status ret InpStat endp InpFlush proc near ; function 7 = flush input buffers mov ax,0100h ; return 'done' status ret InpFlush endp Write proc near ; function 8 = write mov ax,0100h ; return 'done' status ret Write endp WriteVfy proc near ; function 9 = write with verify mov ax,0100h ; return 'done' status ret WriteVfy endp OutStat proc near ; function 10 = output status mov ax,0100h ; return 'done' status ret OutStat endp OutFlush proc near ; function 11 = flush output buffers mov ax,0100h ; return 'done' status ret OutFlush endp DevOpen proc near ; function 13 = device open mov ax,0100h ; return 'done' status ret DevOpen endp DevClose proc near ; function 14 = device close mov ax,0100h ; return 'done' status ret DevClose endp RemMedia proc near ; function 15 = removable media mov ax,0100h ; return 'done' status ret RemMedia endp GenIOCTL proc near ; function 16 = generic IOCTL mov ax,0100h ; return 'done' status ret GenIOCTL endp ResetMed proc near ; function 17 = reset media mov ax,0100h ; return 'done' status ret ResetMed endp GetLogDrv proc near ; function 18 = get logical drive mov ax,0100h ; return 'done' status ret GetLogDrv endp SetLogDrv proc near ; function 19 = set logical drive mov ax,0100h ; return 'done' status ret SetLogDrv endp DeInstall proc near ; function 20 = deinstall driver mov ax,0100h ; return 'done' status ret DeInstall endp PartFD proc near ; function 22 = partitionable ; fixed disk mov ax,0100h ; return 'done' status ret PartFD endp FDMap proc near ; function 23 = get fixed disk ; logical unit map mov ax,0100h ; return 'done' status ret FDMap endp Error proc near ; bad command code mov ax,8103h ; error bit and 'done' status ; + "Unknown Command" code ret Error endp Init proc near ; function 0 = initialize mov ax,es:[bx+14] ; get DevHlp entry point mov word ptr devhlp,ax mov ax,es:[bx+16] mov word ptr devhlp+2,ax ; set offsets to end of code ; and data segments mov word ptr es:[bx+14],offset _TEXT:Init mov word ptr es:[bx+16],offset DGROUP:ident ; display sign-on message... push stdout ; standard output handle push ds ; address of message push offset DGROUP:ident push ident_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 mov ax,0100h ; return 'done' status ret Init endp _TEXT ends end Figure 17-25. TEMPLATE.ASM, the source code for a skeleton OS/2 character device driver. LIBRARY TEMPLATE PROTMODE Figure 17-26. TEMPLATE.DEF, the module definition file used during linking of TEMPLATE.SYS. To assemble TEMPLATE.ASM, type the following command: [C:\] MASM TEMPLATE.ASM; Then link TEMPLATE.OBJ with TEMPLATE.DEF and OS2.LIB using the command: [C:\] LINK TEMPLATE,TEMPLATE.SYS,,OS2,TEMPLATE If the assembly and link are successful, you can then copy the new file TEMPLATE.SYS to the root directory of your boot disk, add the line DEVICE=TEMPLATE.SYS to the CONFIG.SYS file, and restart the system to see the sign-on message for the TEMPLATE driver: TEMPLATE Sample OS/2 Device Driver The Device Driver Helper Functions The Device Driver Helper functions, or DevHlps, are kernel services that assist device drivers in carrying out certain critical or complex tasks. Moving functionality that all drivers need into the kernel ensures that these activities are carried out in a uniform and efficient way. It also allows each driver to be smaller, simpler, and easier to debug. Unlike the kernel Application Program Interface (API), for which parameters are passed on the stack and for which each function has a distinct named entry point, the DevHlp interface uses a register-based parameter-passing convention and a common entry point. The driver is provided with a bimodal pointer to the DevHlp entry point in the request packet it receives from the kernel during its initialization. When a driver calls the DevHlp entry point, it selects a particular DevHlp function by the value it places in register DL. It passes additional parameters and addresses in other general registers as demanded by the requested function. The status of a DevHlp function is usually returned to the driver in the Zero flag or Carry flag, error codes in register AX, and addresses in registers DS:SI or ES:DI. Figure 17-27 shows a typical DevHlp call. The fifty-odd DevHlp functions can be grouped conceptually into a few major categories (Figure 17-28 on pp. 38689): Process management Hardware management Memory management Monitor management Semaphore management Request packet management Character queue management Timer management Miscellaneous device helpers ABIOS-related device helpers (IBM PS/2 only) AllocPhys equ 18h ; DevHlp function number devhlp dd ? ; bimodal pointer to ; DevHlp common entry point ; (from Init routine) bufptr dd ? ; receives 32-bit physical ; address of allocated block . . . ; allocate 64 KB of ; memory above 1 MB... mov ax,1 ; AX = high word of size mov bx,0 ; BX = low word of size mov dh,0 ; 0 = above 1 MB boundary mov dl,AllocPhys ; DevHlp 18H = AllocPhys call devhlp ; indirect call to DevHlp ; common entry point jc error ; jump if allocation failed ; save 32-bit physical ; address of memory block mov word ptr bufptr,bx mov word ptr bufptr+2,ax . . . Figure 17-27. Example of DevHlp call. This code allocates 64 KB of physical memory above the 1 MB boundary for use by driver. When the driver wishes to access the memory block, it must use PhysToVirt or PhysToUVirt to convert the physical address stored in bufptr into an appropriate virtual address for the current CPU mode. DevHlp Name DevHlp Modes DevHlp Action Code Process Management Block 04H K U Blocks the calling thread until a timeout occurs or thread is unblocked by Run. DevDone 01H K Int Signals that the specified operation has completed. Kernel unblocks any threads waiting for the operation. Run 05H K Int U Activates a thread which was previously suspended by a call to Block. TCYield 03H K Similar to Yield, but allows other threads to execute only if they have a time-critical priority. Yield 02H K Surrenders the CPU to any other threads of the same or higher priority which are ready to execute. Hardware Management EOI 31H Int Init Issues an End-of-Interrupt to the appropriate 8259 PIC for the specified IRQ level. RegisterStackUsage 38H Init Declares stack requirements for driver's interrupt handler. SetIRQ 1BH K Init Sets the interrupt vector for the indicated interrupt request (IRQ) level to point to the driver's interrupt handler. UnSetIRQ 1CH K Int Init Releases the interrupt vector for the specified IRQ level. Memory Management AllocGDTSelector 2DH Init Allocates one or more GDT selectors for use by the driver. AllocPhys 18H K Init Allocates a block of fixed (not swappable or movable) memory. Caller can specify whether the block should be above or below the 1 MB boundary. FreePhys 19H K Init Releases a block of memory previously allocated with AllocPhys. Lock 13H K Init Locks a memory segment, that is, flags the segment as nonmovable and nonswappable. PhysToGDTSelector 2EH K Int U Init Maps a physical address and length onto a GDT selector. PhysToUVirt 17H K Init Converts a 32-bit physical address to a virtual address accessed through the current Local Descriptor Table (LDT). PhysToVirt 15H K Int Init Converts a 32-bit physical (linear) address to a virtual address. Unlock 14H K Init Unlocks a memory segment. UnPhysToVirt 32H K Int Init Signals that the virtual addresses previously obtained with PhysToVirt can be reused. VerifyAccess 27H K Verifies that a user process has access to a segment. VirtToPhys 16H K Init Converts a virtual address (segment:offset or selector:offset) to a 32-bit physical address. Monitor Management DeRegister 21H K Removes a process from a monitor chain. MonFlush 23H K Removes all data from a monitor chain. MonitorCreate 1FH K Init Creates or removes an empty monitor chain. MonWrite 22H K Int U Passes a data record to a monitor chain for filtering. Register 20H K Adds a process to a monitor chain. Semaphore Management SemClear 07H K Int U Clears a semaphore, restarting any threads that were blocking on the semaphore. SemHandle 08H K Int Converts a process's handle for a system semaphore to a virtual address that the driver can use at interrupt time. SemRequest 06H K U Claims (sets) a semaphore. If the semaphore is already set, blocks the thread until the semaphore is available. Request Packet Management AllocReqPacket 0DH K Allocates a block of memory large enough to hold the largest possible request packet and returns a bimodal pointer. FreeReqPacket 0EH K Releases a request packet previously allocated with AllocReqPacket. PullParticular 0BH K Int Removes a selected request packet from the driver's linked list. PullReqPacket 0AH K Int Removes the oldest request packet from the driver's linked list. PushReqPacket 09H K Adds a request packet to the driver's linked list of packets. SortReqPacket 0CH K Inserts a request packet into the driver's linked list of packets, sorted by sector number. Character Queue Management QueueFlush 10H K Int U Resets the pointers to the specified buffer. QueueInit 0FH K Int U Init Establishes a ring buffer for storage of characters and initializes its pointers. QueueRead 12H K Int U Returns and removes a character from the specified buffer. QueueWrite 11H K Int U Puts a character into the specified buffer. Timer Management ResetTimer 1EH K Int Init Removes a timer tick handler from the system's list of such handlers. SchedClockAddr 00H K Init Obtains the address of the kernel's routine to be called on each clock tick. Used only by clock driver. SetTimer 1DH K Init Adds a timer tick handler, to be called on every timer tick, to the system's list of such handlers. TickCount 33H K Int U Init Adds a timer tick handler, to be called at specified intervals, to the system's list of such handlers, or modifies the interval at which an existing handler is called. Miscellaneous Device Helpers AttachDD 2AH K Init Obtains inter-driver communication (IDC) entry point for another driver. GetDOSVar 24H K Init Returns addresses of kernel variables and entry points. ProtToReal 30H K Int Switches the CPU into real mode. RealToProt 2FH K Int Switches the CPU into protected mode. ROMCritSection 26H U Protects ROM BIOS code from interrupts that might cause a context switch. SendEvent 25H K Int Signals the kernel that a key requiring special handling has been detected. Used only by keyboard driver. SetROMVector 1AH K Init Captures an interrupt vector normally used in real mode to invoke a ROM BIOS function, returning the previous contents of the vector for chaining. ABIOS-related Device Helpers (IBM PS/2 only) ABIOSCall 36H K Int U Init Invokes an Advanced BIOS (ABIOS) service on behalf of the driver. ABIOSCommonEntry 37H K Int U Init Transfers to an ABIOS Common Entry point on behalf of the driver. FreeLIDEntry 35H K U Init Releases a logical ID (LID) for a physical device. GetLIDEntry 34H K U Init Obtains a logical ID (LID) for a physical device, for use with subsequent ABIOS calls. Figure 17-28. DevHlp functions by category, their function numbers, and the driver contexts in which they may be used (K = kernel mode, Int = interrupt mode, U = user mode, Init = initialization mode). Some service categories are used by every OS/2 device driver because they allow the driver to perform overlapped, interrupt-driven I/O operations in a "well-behaved" manner, or because they provide access to memory addresses that the driver could not reach otherwise. The remainder are present only for convenience or to assist in the construction of unusual or highly complex drivers. The programming reference for the DevHlp services is in Section VI, beginning on p. 657. Process Management The basic technique by which a driver performs overlapped or asynchronous I/O is a simple one. When the strategy routine of the driver is called, it sets up the I/O operation (or adds it to the list of pending operations if one is already in progress) and simply returns to the OS/2 kernel with the Done bit cleared in the request packet status word. The kernel then suspends the thread that initiated the I/O request. When the I/O operation is eventually completed, the driver's interrupt routine updates the request packet that initiated the operation and then calls the DevHlp routine DevDone. This signals the kernel to awaken the thread that owns the I/O request, which can then perform any necessary post-I/O processing (such as translating the driver's status information into appropriate values to be returned to an application program). The driver can also use the DevHlp functions Block and Run to suspend a thread explicitly while an I/O operation is in progress, particularly if the driver must execute a lengthy amount of code after the operation is complete. Putting such code in the strategy routine is preferable to putting it in the interrupt routine because other lower-priority interrupts are locked out by the hardware while the interrupt routine is executing. The strategy routine sets up the I/O operation and then calls Block with a 32-bit identifier of its own choosing. When the interrupt routine notes that the I/O operation is complete, it calls Run with the same 32-bit identifier. This causes the kernel to wake up the original thread within the strategy routine, which can proceed with post-I/O processing and then exit to the kernel with the request packet's Done bit set. The biggest problem with this method is ensuring the uniqueness of the 32-bit value that the driver uses to associate a pair of Block and Run calls; the call to Run wakes up all threads that have blocked with that particular 32-bit identifier. The additional DevHlp functions Yield and TCYield are provided for drivers that need to transfer long strings of data from one memory location to another at task time (such as a RAM disk) or whose devices do not support interrupts. A call to Yield simply tells the kernel's scheduler to give all other threads with the same or higher priority a chance to run before returning control to the currently executing thread, whereas TCYield allows only those other threads which have a "time-critical priority" to take a turn. A driver can check the value of YieldFlag or TCYieldFlagtwo variables whose addresses are returned by another DevHlp, GetDOSVarto determine whether any other threads are waiting for the CPU, and it can then bypass the call to Yield or TCYield if no other thread is eligible to execute. To avoid interference with the proper operation of other drivers, make such a check (and possible Yield) at least every 3 milliseconds. Hardware Management Because the strategy and interrupt routines of device drivers execute at the highest privilege level, they have a free hand with the machine if they want it; two different drivers that access the same hardware addresses can theoretically come into conflict. However, OS/2 provides DevHlps to arbitrate interrupt vector and 8259 programmable interrupt controller (PIC) usage for cooperating drivers. Drivers that do not use these DevHlps to gain access to the hardware resources they need might not work properly on future 80386-specific versions of OS/2. The DevHlp functions SetIRQ and UnSetIRQ assist drivers in the capture or release of interrupt vectors. When a driver calls SetIRQ, it also specifies whether it is willing to share the level with another driver. If it specifies that it wants to own the interrupt exclusively, subsequent requests for the same IRQ level by other drivers will fail; if another driver has already signed up for the interrupt, the request by the current driver will fail. In OS/2 version 1.1 a driver that uses interrupts must also declare the stack requirements of its interrupt handler during initialization by calling the DevHlp RegisterStackUsage. When a hardware interrupt occurs, a dispatcher in the OS/2 kernel initially receives control, saves all registers, sets the DS register to point to the driver's near data segment, and then transfers control to the driver's interrupt handler. The handler services its device, starts another I/O operation if any are waiting, calls the DevHlp function EOI to dismiss the interrupt at the 8259 PIC, and then returns to the kernel. Although a driver can access its own device's ports directly, you should reserve manipulation of the PIC for the kernel to isolate a driver from the interrupt architecture. Memory Management A driver specifies the amount of storage to reserve for its code and data segments in the information it returns to the kernel at Init time. If the driver needs additional memory for tables, buffers, or other data structures, it can use the DevHlp functions AllocPhys and FreePhys to dynamically allocate and release such memory. The memory assigned to the driver by AllocPhys is not movable or swappable, and the driver can specify whether it wants the memory to be located above or below the 1 MB boundary. Memory addressing is one of the trickiest aspects of an OS/2 driver's operation. To execute and handle interrupts in either protected mode or real mode, a driver must be prepared to encounter three types of addressesprotected mode selector:offset pairs, real mode segment:offset pairs, and the physical 32-bit addresses used by DMA channels and by some devices. Further complicating the situation are the activities of the operating system's virtual memory manager, which shuffles segments around to collect unused fragments and swaps out segments to disk. The OS/2 kernel takes several measures to shield drivers from most of the potential problems with memory addressing. First, whenever the kernel requests an explicit transfer operation from a driver using a request packet that contains the Read or Write command codes, it passes the driver a 32-bit physical address which has already been "locked" (meaning that the virtual memory manager has been notified that the memory in question should not be moved or swapped). In addition, the kernel uses bimodal pointers when it passes the driver an address of a structure (such as a request packet) or procedure (such as the DevHlp entry point) that the driver needs to access directly. The kernel also provides six DevHlp functions that help a driver make address conversions: AllocateGDTSelector, PhysToGDTSelector, PhysToUVirt, PhysToVirt, UnPhysToVirt, and VirtToPhys. These functions assist a driver by converting physical 32-bit addresses to virtual addresses (segment:offset or selector:offset pairs) and back again. The first three are used to access static structures (such as memory allocated by AllocPhys or an adapter's memory-mapped I/O locations). PhysToVirt and UnPhysToVirt are used when a driver needs temporary access to data that is transient or owned by a process, allowing the driver to translate the physical address in a Read or Write request into a virtual address appropriate to the current CPU mode. Finally, to provide for cases in which a memory address is passed to a driver in an IOCTL data packet or by some other channel of communication, the OS/2 kernel also provides the DevHlp functions Lock, Unlock, and VerifyAccess. The driver first calls VerifyAccess with the selector, offset, and length of the memory involved in the requested operation to confirm that the application is entitled to access that memory. If VerifyAccess succeeds, the driver calls the Lock function to immobilize the segment and VirtToPhys to obtain the physical address if necessary; then it proceeds with the I/O operation, calling Unlock afterwards to put the segment back under the control of the system's memory manager. Drivers must avoid blocking between the VerifyAccess and Lock calls so that no context switch can occur that might make the VerifyAccess result invalid. The use of PhysToVirt is most interesting when the driver is entered in real mode while the memory address to be accessed lies above the 1 MB boundary. On an IBM ATtype machine, PhysToVirt uses an undocumented 80286 instruction (LOADALL) to set the CPU's "shadow" descriptor registers with values that allow the memory access to take place without a mode switch. When you POP or MOV a selector into a segment register, the CPU loads the segment base, length, and so forth from the corresponding descriptor into "shadow" registers. In effect, the CPU uses these otherwise inaccessible registers to cache the descriptor information on-chip. This allows most protected mode memory accesses to be as fast as they are in real mode. The addressability of the memory location is valid only as long as the associated segment register (which does not contain any physically meaningful value) is not reloaded; thus, interrupts must remain masked, the segment register should not be pushed and popped, and no DevHlp calls other than another PhysToVirt can be made until the driver completes the memory access. On an 80386-based or IBM PS/2type machine, which supports faster mode switching, PhysToVirt switches the CPU into protected mode and returns a normal selector for the memory location of interest. At first glance, this method of obtaining addressability for the data may appear more attractive, but it actually imposes a much greater burden on the driver because it invalidates any pointers previously obtained from PhysToVirt that did not cause a mode switch. The driver must be constantly on the lookout for an unexpected change in modes and must be prepared to backtrack and recalculate any saved virtual addresses. In either hardware environment, the pool of selectors available to PhysToVirt is limited. When a program finishes with virtual addresses it obtained from PhysToVirt, it should notify the system that the selectors can be reused by calling UnPhysToVirt. If an earlier call to PhysToVirt resulted in a mode switch, UnPhysToVirt also restores the original CPU mode. Monitor Management The five monitor-related DevHlpsMonitorCreate, Register, DeRegister, MonWrite, and MonFlushhelp the driver carry out its role in OS/2's support for device monitors. MonitorCreate establishes or destroys a chain of monitor buffers; Register and DeRegister add or remove a specific application's buffers from the monitor buffer chain. The driver passes characters through the monitor buffer chain by calling MonWrite. The kernel calls a notification routine within the driver when data emerges from the end of the buffer chain. The driver can notify all device monitor applications to reinitialize their pointers and buffers by calling MonFlush. Chapter 18 covers device monitors, as well as device driver support for monitors, in more detail. Semaphore Management The DevHlp functions SemRequest and SemClear, which can be called by the driver to obtain or release ownership of a semaphore, are the equivalent of the API functions DosSemRequest and DosSemClear described in Chapter 13. These operate on either RAM semaphores or system semaphores. RAM semaphores can be located in the data segment of either a driver or an application program. (When an application RAM semaphore is used, its address would typically be passed to the driver in a DosDevIOCtl call.) RAM semaphores are useful for signaling between different components of a driver or between a closely coupled driver and application, or for synchronizing access to driver resources that are nonreentrant. System semaphores can be created or opened only by an application, not by a driver. To use system semaphores for communication between a driver and an application, the application must first obtain a handle for the semaphore with DosOpenSem or DosCreateSem and pass it to the driver with a DosDevIOCtl call. The driver then uses the DevHlp function SemHandle at task time to obtain a new semaphore handle that can be used at interrupt time. Once both the driver and application have a usable handle for the semaphore, either can use the semaphore to signal or block the other. Request Packet Management Another set of DevHlps assists the driver in maintaining and sorting a linked list of request packets for pending I/O operations. The driver must provide a doubleword of storage (initialized to zero) that the DevHlp routines can use as a pointer to the head of the request packet queue. A reserved doubleword field within the packets is used to chain one to another. Request packets are added to the queue by PushReqPacket (which adds to the queue end) or by SortReqPacket (which inserts a Read or Write packet into the queue based on its logical sector number). A driver can remove packets from the queue with PullReqPacket (which returns the oldest request) or with PullParticular (usually employed only when a process has terminated with I/O still pending). The DevHlp functions AllocReqPacket and FreeReqPacket let a driver dynamically obtain and release storage for additional request packets. A driver might use these routines in cases where it needs to decompose an I/O request into two or more requests that it can queue independentlyfor example, it might convert a Read request packet into a seek followed by a transfer to avoid issuing redundant seek commands if multiple Read requests are pending for the same track. AllocReqPacket returns a bimodal pointer to a block of memory that is exactly large enough for the largest possible request packet (in contrast to AllocPhys, which returns a 32-bit physical address and can be used to allocate blocks of any size). Request packets are a relatively scarce resource. A driver that uses AllocReqPacket should be prepared for the function to fail and should have a recovery strategy that will not degrade system performance or cause I/O requests to fail. Character Queue Management The kernel also supplies a group of DevHlps for managing character queues; these routines provide simple support for a ring buffer of characters which are received or transmitted from a device. A character queue is declared in the following form: QSize dw n ; size of queue in bytes QChrOut dw 0 ; index of next character out QCount dw 0 ; count of characters in buffer Qbuff db n dup (?) ; storage starts here Each character queue has, in effect, two pointers: an "out" pointer, which points to the oldest character in the buffer, and an "in" pointer, which points to the buffer position for the next character to be added to the buffer. When both pointers are equal, the buffer is empty. These two pointers do not exist in storage but are formed as needed by combining the values in QChrOut and QCount with the base address Qbuff. A character queue is initialized by a call to the DevHlp function QueueInit. You can add characters to the ring buffer with QueueWrite and remove them with the DevHlp function QueueRead. The QueueFlush routine resets the pointers to the specified character queue, effectively discarding its contents. A typical character device driver has two character queues: one for received characters and one for transmitted characters. The driver's interrupt routine adds characters to the receive queue, and its strategy routine removes them with a call to the Read (command code 4) function. Characters are added to the write queue by the strategy routine's Write (command code 8) function if the device is busy or if characters are already waiting in the queue, and they are removed from the queue by the interrupt routine. Timer Management The timer-related DevHlps assist a driver in performing polled I/O on devices that do not support interrupts, or in setting up a "watchdog" timer to detect lost interrupts or I/O operations that never complete. The function SetTimer is called with the address of a routine within the driver that must execute on each timer tick; this is the equivalent of chaining onto Int 1CH (the ROM BIOS timer tick vector) under MS-DOS. The DevHlp function TickCount is a more general version of SetTimer; rather than entering the driver's timer tick handler on every tick, it lets you specify the number of ticks between entries. The driver can call ResetTimer to remove its timer tick handler from the system's list of all such handlers. The function SchedClockAddr is used only by the system's clock driver during its initialization. It supplies a pointer to the routine in the kernel that should be called on each clock tick. The kernel's routine then distributes the clock ticks through the system wherever they are needed to the scheduler, for example, and to the timer tick handlers of other device drivers that have been registered with SetTimer or TickCount. Miscellaneous Device Helpers The remaining DevHlpsRealToProt, ProtToReal, SendEvent, GetDOSVar, SysTrace, SetROMVector, ROMCritSection, AttachDD, and the ABIOS-related servicesdo not fall into any convenient category. As their names imply, RealToProt and ProtToReal allow the driver to request a CPU mode switch. Placing the mode-switching logic inside the kernel ensures that the mode switching is done properly and that it takes advantage of any special external hardware support (such as exists on the PS/2) or CPU support (such as that provided by the 80386) that can speed up the operation. SendEvent is normally used only by the system's keyboard driver (KBD$). It allows the driver to signal the kernel when certain special keys are detected (such as Ctrl-Break or the Task Manager hot keys), so that the kernel can take immediate action rather than waiting for the keycode to percolate through the driver's character input buffer. GetDOSVar returns bimodal addresses for several significant system variables and tables, including the Global Information Segment, the Local Information Segment, the "reboot" routine, and the system's Yield and TCYield flags (set when at least one thread is ready to execute). The driver can call GetDOSVar to obtain the pointers at its initialization time and store them for later use. AttachDD allows a driver to obtain the inter-driver communication (IDC) entry point for a previously loaded character device driver. It can then invoke the other driver directly at any time with a far call. Parameter passing for IDC can take any form and is not standardized because the OS/2 kernel is not involved. AttachDD identifies drivers by the 8-character device name in their headers, so a block driver that wants to make an IDC entry point available needs two headers: one for the usual purposes and a second that has the character device attribute bit set and that contains a logical device name and the IDC entry point. SetROMVector and ROMCritSection assist driverssuch as the keyboard, video, serial port, and printer driverswhich must support ROM BIOS calls by real mode applications; that is, drivers that can be entered in user mode. At initialization time, the driver must use SetROMVector to take over the software interrupt vector for the ROM BIOS services that use its device. When a real mode program makes a ROM BIOS call, the driver receives control, at which point it can use ROMCritSection to disable session switching, and then chain to the original ROM BIOS code or provide the same functionality within its own code. The Processing of a Typical I/O Request An application program requests an I/O operation from OS/2 by pushing appropriate values and addresses onto the stack and executing a far call to a named API entry point. OS/2 inspects its internal tables, searches the chain of device driver headers if necessary, and determines which device driver should receive the I/O request. OS/2 then creates a request packet in a reserved area of memory. The system transforms disk I/O requests from file and record information into logical sector requests based on its interpretation of the disk's directory and file allocation table. (OS/2 can locate these disk structures using the information that is returned by the driver from a previous Build BPB call, and it issues additional driver read requests if necessary to bring their sectors into memory.) After the request packet is prepared, OS/2 calls the device driver's strategy entry point, passing the address of the packet in registers ES:BX. If the strategy routine can dispose of the request immediately (as it typically can with a status request or a request for data which is already in the driver's own buffers), it places the appropriate information into the request packet or other buffer, sets the Done bit to indicate that the request is complete, and returns control to the kernel. Otherwise, the strategy routine can suspend execution of the requesting thread by two different methods. It can return to the kernel with the Done bit in the request header cleared and rely on the interrupt routine to call DevDone later. This method has the side effect of unblocking the thread that issued the I/O request. Alternatively, it can call the DevHlp Block and wait for the interrupt routine to wake it up by calling the DevHlp Run. In this case, the strategy routine sets the Done bit in the request packet status word before it exits, and no call to DevDone is needed. When the I/O is finished, the kernel transforms the request status to an application level error code if necessary and updates the application's saved AX register with the appropriate status code. It places any other necessary information (such as the number of bytes actually transferred on a DosRead call) into the variables whose addresses were passed on the stack in the original API call. At this point the thread which made the I/O request exits the kernel and resumes execution within the application. Figure 17-29 provides a sketch of this entire flow of control and data. Ŀ Application program  Far call to API dynamic link Read status returned in entry point for DosRead register AX; length passed in caller's variable Ŀ OS/2 kernel  Far call to strategy entry Status returned to OS/2 in point ES:BX = request packet request packet; data placed with command code = 4 at address indicated by kernel Ŀ Device driver  Commands issued to I/O ports; Data transferred from request read of physical head, device to memory track, sector Ŀ Physical device Figure 17-29. The processing of a typical I/O request from an application program. Note that a single API call by an application program can result in many requests from the kernel to the device driver. For example, if an application invokes DosOpen to open or create a file, OS/2 might have to issue multiple sector Read requests to the driver while searching the directory for the filename. Similarly, a DosRead request on an open file might obligate OS/2 to load several FAT sectors while locating the correct cluster, before the actual file data can be transferred into memory. Building a Device Driver The creation of a full-fledged OS/2 device driver, particularly a driver for a high speed mass storage device with multiple units, is a large and complex project. Because a driver can contain multiple threads of execution at any given time, you must guard against the subtle bugs that result from improper handling of interrupts, incorrect synchronization of driver resources, race conditions, and routines that are not completely reentrant. In accordance with Murphy's Law, such bugs often go undiscovered until the driver is subjected to the end user's typical job mix, which stresses the driver in ways you never imagined. You can minimize the unpleasant surprises in your OS/2 device driver by adopting an approach that stresses defensive, conservative, structured coding techniques and stepwise refinement. These techniques are valuable in any software development project, of course, but they can make the difference between success and failure in driver development. Segments and Subroutines OS/2 device drivers are usually written as "small model" Microsoft MASM programs with one code segment and one data segment. The data segment, with the device driver header at its beginning, should be declared before the code segment. The structure of the resulting executable file built by the Linker is shown in Figure 17-30 on the following page. The driver should not contain a stack segmenta stack is provided by the system. The two primary components of a driver are its strategy and interrupt routines. They, in turn, call other routines which implement the various driver functions (read, write, status, and so forth), allocate or release memory, translate addresses, and manage the driver's request queue. You should design each procedure to be reentrant, if possible, even if you see no obvious need for reentrancy; it is much easier to code a routine properly that uses local variables from the outset than to graft them on later. Any action on global data items by a procedure should be clearly documented and synchronized (if necessary) by semaphores. Start Ŀ of EXE file header file ĴĿ Device driver header Ĵ Driver data Resident driver data segment Ĵ Initialization data (discarded) Ĵ͵ Driver Resident driver code code End Ĵ segment of Initialization code (discarded) file  Figure 17-30. Structure of an OS/2 installable device driver file. OS/2 drivers are linked as "small model" EXE files, containing one code segment and one data segment. By convention, driver files always have the extension SYS. Note that code and data used only by the driver's initialization routine can be positioned at the end of the two segments. To minimize the driver's memory overhead, they can then be discarded after driver initialization is completed. Stepwise Refinement #1 The first version of a new device driver should perform all its operations by polled I/O within the strategy routine, that is, at task time. By starting out with a polled I/O version, you can validate the elements of the driver which are purely related to control of the hardwareissuing the right commands to the correct I/O ports in the proper orderwithout worrying about CPU mode changes, reentrancy, and interrupts. The strategy routine runs with kernel privilege and with interrupts enabled; it has full access to all I/O ports and memory addresses and is never preempted. It is entered from the kernel with a valid stack, register DS pointing to the driver's data segment, and registers ES:BX containing a bimodal address of a request packet. The strategy routine exits to the kernel with a far return; no registers need be preserved. To simplify initial debugging of a new driver, you can implement a minimum set of functionssufficient to read and write the physical deviceand provide stubs for the less vital services. Depending on the service it represents, a stub can return a "reasonable" result, do nothing and return a Done status, or return an Error plus Done status. Character device drivers must support, at minimum, the device driver command code functions Init, Read, and Write. Write with Verify can be aliased to Write, and DeInstall can return an error. If your driver performs internal buffering of data, you should also fully implement Input Status, Output Status, Nondestructive Read, Flush Input Buffers, and Flush Output Buffers. If your driver does not provide internal buffering, these five functions can simply return with the status word set to 0100H (Busy bit clear, Done bit set). You need not provide support for the Device Open and Device Close functions unless bit 11 is set in the device header attribute word. The Generic IOCtl function should be stubbed out to return an error; leave the actual implementation for later. In a rudimentary block device driver, you must implement functions Init, Media Check, Build BPB, Read, and Write to support file storage by OS/2. As with the character drivers, you can temporarily alias Write with Verify to Write. Reset Media can simply return Done status, Get Logical Device and Set Logical Device should clear the unit field of the request packet to zero and return Done status, and the Partitionable Fixed Disk and Get Fixed Disk/Logical Unit Map functions can be stubbed out to return an error. The Device Open, Device Close, and Removable Media functions need not be implemented unless bit 11 is set in the header attribute word. Stepwise Refinement #2 Once you establish that the basic device control works properly in a polled I/O environment, add interrupt handling to the driver. This change enlarges and complicates the driver considerably. Multiple requests might be pending at one time, and both the strategy and interrupt routines must be capable of manipulating the request list and initiating I/O. In addition, the CPU mode at strategy time for a given request might well be different than the mode at interrupt time. Like the strategy routine, the interrupt routine runs with kernel privilege and has full access to all I/O ports and memory addresses. It is entered by a far call from the kernel with a valid stack and with register DS pointing to the driver's data segment. If the driver owns the interrupt level exclusively, the interrupt routine is entered with interrupts masked; otherwise, it is entered with interrupts enabled. The interrupt routine may itself be interrupted only by other devices with higher priority, or by devices with the same priority (including its own device) once an EOI has been signaled to the 8259 PIC. The interrupt routine must test its device to verify that it generated the interrupt. If it owns the interrupt, it must service the device, start another I/O operation if any are waiting, call the DevHlp function EOI to dismiss the interrupt at the 8259 PIC, and return to the kernel with the Carry flag clear (0). If it does not own the interrupt, it should exit to the kernel with the Carry flag set (1). Your first priority when adding interrupt handling to your driver is to decide which command code functions can be completed entirely at task time by the strategy routine and which functions must be deferred to be completed by the interrupt routine. In most cases, for both character and block device drivers, only Read and Write requests need to be carried out in overlapped fashion; the request packets for all other driver operations can be processed when they are received. If the device is idle when the strategy routine is called with a Read or Write request, it should initiate the I/O immediately and then suspend the requesting thread so that the scheduler can dispatch other threads that are ready to execute while the I/O is in progress. If the device is busy, the strategy routine should simply add the request packet to the driver's request packet queue and then suspend the thread. To optimize access time, block device Read and Write requests can be queued and executed in any order, but character device Read and Write operations must be completed in the order they are received to avoid mixed output. As we have noted, the strategy routine can suspend execution of a requesting thread either by exiting to the kernel with the Done bit in the status word cleared or by explicitly putting the thread to sleep with a call to the DevHlp function Block. If the strategy routine blocks within itself, it must be fully reentrant because it might be reentered by the kernel as a result of new I/O requests from other threads. Any functions called by the strategy routine must be reentrant anyway if they can also be called by interrupt routines. The strategy routine should mask interrupts while it is manipulating its work list and while it is checking the device status so that it does not get into a race condition with its own interrupt handler. Stepwise Refinement #3 In the final development step, tune the driver to optimize scheduling of multiple pending I/O requests, to minimize both the periods of interrupt masking and total time to service an interrupt, and to yield the CPU at suitable intervals (at least every 3 milliseconds) when large amounts of data must be shifted in memory. You can also add enhancements such as watchdog timers for lost interrupts and DosDevIOCtl support once the driver has proven fundamentally sound. Custom replacements for the system's keyboard, printer, and serial port drivers must support all the DosDevIOCtl functions documented in the OS/2 manuals, so that applications which rely on these functions will not fail unexpectedly. IOCTL support for other custom drivers is generally optional, except that block device drivers should always support DosDevIOCtl Category 8, Function 63H (Get Device Parameters), which is used by CHKDSK. If this function is absent, CHKDSK terminates with the error message The System Cannot Accept the Command after displaying the logical drive's volume label and date. Drivers for Noninterrupting Devices Drivers whose devices are not capable of generating interrupts can perform polled I/O by two methods. If the peripheral is a DMA-capable block device or a relatively low speed character device, the driver can use the DevHlp functions TickCount or SetTimer to register a handler that receives control on each system timer tick (or multiple thereof), and it can have that handler poll the device. (The length of the timer tick in milliseconds can be obtained from the global information segment.) This relatively benign technique does not appreciably degrade system performance. If the device requires programmed I/O and transfers data in bursts which cannot be interrupted or deferred, or if it is a high speed character device, the driver must perform the I/O in a status-bound loop in the strategy routine. Such a driver almost inevitably degrades overall system performance because it interferes with the prompt initiation of I/O operations and the servicing of interrupts for other devices. The driver can keep the damage to a minimum by calling Yield or TCYield at intervals not greater than 3 milliseconds, even if this forces it to abort and restart some of its own I/O operations. A Sample Block Device Driver The listing TINYDISK.ASM (Figure 17-31 on the following page) contains the source code for a simple installable RAM disk called TINYDISK.SYS. Its module definition file, TINYDISK.DEF, is shown in Figure 17-32 on p. 417. This driver demonstrates the essential features of an OS/2 block device driver, and it illustrates the use of some crucial DevHlp functions such as AllocPhys and PhysToVirt. title TINYDISK -- RAM Disk Device Driver page 55,132 .286 ; ; TINYDISK.ASM ; ; A sample OS/2 block driver that installs a 64 KB RAM disk. ; ; Assemble with: C> masm tinydisk.asm; ; Link with: C> link tinydisk,tinydisk.sys,,os2,tinydisk ; ; To install the driver, add "DEVICE=TINYDISK.SYS" to CONFIG.SYS ; and reboot. ; ; Copyright (C) 1988 Ray Duncan ; maxcmd equ 26 ; maximum allowed command code secsize equ 512 ; bytes/sector, IBM compatible media stdin equ 0 ; standard device handles stdout equ 1 stderr equ 2 cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed PhysToVirt equ 15h ; DevHlp services UnPhysToVirt equ 32h VerifyAccess equ 27h AllocPhys equ 18h extrn DosWrite:far DGROUP group _DATA _DATA segment word public 'DATA' ; device driver header... header dd -1 ; link to next device driver dw 0080h ; device attribute word ; bits 7-9 = driver level dw Strat ; Strategy entry point dw 0 ; reserved db 0 ; units (set by OS/2) db 15 dup (0) ; reserved devhlp dd ? ; DevHlp entry point wlen dw ? ; receives DosWrite length dbase dd ? ; 32-bit physical address, ; base of RAM disk storage xfrsec dw 0 ; current sector for transfer xfrcnt dw 0 ; sectors successfully transferred xfrreq dw 0 ; number of sectors requested xfraddr dd 0 ; working address for transfer array dw bpb ; array of pointers to BPB ; for each supported unit bootrec equ $ ; logical sector 0 boot record jmp $ ; JMP at start of boot sector, nop ; this field must be 3 bytes db 'IBM 10.1' ; OEM identity field ; ---BIOS Parameter Block----- bpb dw secsize ; 00H bytes per sector db 1 ; 02H sectors per cluster dw 1 ; 03H reserved sectors db 1 ; 05H number of FATs dw 64 ; 06H root directory entries dw 128 ; 08H total sectors db 0f8h ; 0AH media descriptor dw 1 ; 0BH sectors per FAT dw 1 ; 0DH sectors per track dw 1 ; 0FH number of heads dd 0 ; 11H hidden sectors dd 0 ; 15H large total sectors (if ; word at offset 08H = 0) db 6 dup (0) ; 19H reserved ; ---End of BPB, 31 bytes----- bootrec_len equ $-bootrec ; length of boot sector data ; additional words needed by ; Generic IOCTL Cat 8 Function 63H ; Get Device Parameters call dw 0 ; number of cylinders db 7 ; device type = unknown dw 1 ; device attribute word gdprec_len equ $-bpb ; length of Generic IOCTL buffer ; Strategy routine dispatch table ; for request packet command code... dispch dw Init ; 0 = initialize driver dw MediaChk ; 1 = media check on block device dw BuildBPB ; 2 = build BIOS parameter block dw Error ; 3 = reserved dw Read ; 4 = read (input) from device dw Error ; 5 = nondestructive read dw Error ; 6 = return input status dw Error ; 7 = flush device input buffers dw Write ; 8 = write (output) to device dw Write ; 9 = write with verify dw Error ; 10 = return output status dw Error ; 11 = flush output buffers dw Error ; 12 = reserved dw Error ; 13 = device open dw Error ; 14 = device close dw Error ; 15 = removable media dw GenIOCTL ; 16 = generic IOCTL dw Error ; 17 = reset media dw GSLogDrv ; 18 = get logical drive dw GSLogDrv ; 19 = set logical drive dw Error ; 20 = deinstall dw Error ; 21 = reserved dw Error ; 22 = partitionable fixed disks dw Error ; 23 = get fixed disk unit map dw Error ; 24 = reserved dw Error ; 25 = reserved dw Error ; 26 = reserved ; start of data discarded ; after initialization ident db cr,lf,lf ; successful installation message db 'TINYDISK 64 KB RAM disk for OS/2' db cr,lf db 'RAM disk will be drive ' drive db 'X:' db cr,lf ident_len equ $-ident abort db cr,lf ; aborted installation message db 'TINYDISK installation aborted' db cr,lf abort_len equ $-abort volname db 'TINYDISK ' ; volume label for RAM disk db 08h ; attribute byte db 10 dup (0) ; reserved area dw 0 ; time = 00:00 dw 1021h ; date = January 1, 1988 db 6 dup (0) ; reserved area volname_len equ $-volname _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP,es:NOTHING Strat proc far ; Strategy entry point ; ES:BX = request packet mov di,es:[bx+2] ; get command code from packet and di,0ffh cmp di,maxcmd ; supported by this driver? jle Strat1 ; jump if command code OK call Error ; bad command code jmp Strat2 Strat1: add di,di ; go to command code routine call word ptr [di+dispch] Strat2: ; return with AX = status mov es:[bx+3],ax ; put status in request packet ret ; return to OS/2 kernel Strat endp ; Command code routines are called by the Strategy routine ; via the Dispatch table with ES:BX pointing to the request ; header. Each routine should return ES:BX unchanged ; and AX = status to be placed in request packet: ; 0100H if 'done' and no error ; 0000H if thread should block pending interrupt ; 81xxH if 'done' and error detected (xx = error code) MediaChk proc near ; function 1 = media check ; return 'not changed' code mov byte ptr es:[bx+0eh],1 mov ax,0100h ; return 'done' status ret MediaChk endp BuildBPB proc near ; function 2 = build BPB ; put BPB address into ; request packet mov word ptr es:[bx+12h],offset DGROUP:bpb mov word ptr es:[bx+14h],ds mov ax,0100h ; return 'done' status ret BuildBPB endp Read proc near ; function 4 = read push es ; save request packet address push bx call setup ; set up transfer variables Read1: mov ax,xfrcnt ; done with all sectors yet? cmp ax,xfrreq je read2 ; jump if transfer completed mov ax,ds ; set ES = DGROUP mov es,ax mov ax,xfrsec ; get sector number call MapDS ; and map it to DS:SI ; (may force mode switch) push es ; save DGROUP selector ; convert destination physical ; address to virtual address... mov bx,word ptr es:xfraddr mov ax,word ptr es:xfraddr+2 mov cx,secsize ; segment length mov dh,1 ; leave result in ES:DI mov dl,PhysToVirt ; function number call es:devhlp ; transfer to kernel mov cx,secsize ; transfer logical sector from cld ; RAM disk to requestor rep movsb pop ds ; restore DGROUP addressing sti ; PhysToVirt may mask interrupt inc xfrsec ; advance sector number ; advance transfer address add word ptr xfraddr,secsize adc word ptr xfraddr+2,0 inc xfrcnt ; count sectors transferred jmp read1 Read2: ; all sectors transferred mov dl,UnPhysToVirt ; function number call devhlp ; transfer to kernel pop bx ; restore request packet address pop es mov ax,xfrcnt ; put actual transfer count mov es:[bx+12h],ax ; into request packet mov ax,0100h ; return 'done' status ret Read endp Write proc near ; functions 8,9 = write push es ; save request packet address push bx call setup ; set up transfer variables Write1: mov ax,xfrcnt ; done with all sectors yet? cmp ax,xfrreq je write2 ; jump if transfer completed mov ax,xfrsec ; get sector number call MapES ; map it to ES:DI ; (may force mode switch) push ds ; save DGROUP selector ; convert source physical ; address to virtual address... mov bx,word ptr xfraddr mov ax,word ptr xfraddr+2 mov cx,secsize ; segment length mov dh,0 ; leave result in DS:SI mov dl,PhysToVirt ; function number call devhlp ; transfer to kernel mov cx,secsize ; transfer logical sector from cld ; requestor to RAM disk rep movsb pop ds ; restore DGROUP addressing sti ; PhysToVirt might have masked inc xfrsec ; advance sector number ; advance transfer address add word ptr xfraddr,secsize adc word ptr xfraddr+2,0 inc xfrcnt ; count sectors transferred jmp write1 Write2: ; all sectors transferred mov dl,UnPhysToVirt ; function number call devhlp ; transfer to kernel pop bx ; restore request packet address pop es mov ax,xfrcnt ; put actual transfer count mov es:[bx+12h],ax ; into request packet mov ax,0100h ; return 'done' status ret Write endp GenIOCTL proc near ; function 16 = Generic IOCtl push es ; save request packet address push bx mov ax,8103h ; assume unknown command ; Get Device Parameters call? cmp byte ptr es:[bx+0dh],8 jne Gen9 ; no, set 'done' and 'error' cmp byte ptr es:[bx+0eh],63h jne Gen9 ; no, set 'done' and 'error' ; verify user's access mov ax,es:[bx+15h] ; selector mov di,es:[bx+13h] ; offset mov cx,gdprec_len ; length to be written mov dh,1 ; need read/write access mov dl,VerifyAccess ; function number call devhlp ; transfer to kernel mov ax,810ch ; if no access, exit with jc Gen9 ; 'done' and general failure ; get destination address les di,dword ptr es:[bx+13h] ; copy device info to caller mov si,offset DGROUP:bpb mov cx,gdprec_len ; length of parameter block cld rep movsb mov ax,0100h ; set 'done' status Gen9: pop bx ; restore request packet address pop es ret ; and return with status GenIOCTL endp GSLogDrv proc near ; function 18, 19 = get ; or set logical drive ; return code indicating there ; are no logical drive aliases mov byte ptr es:[bx+1],0 mov ax,0100h ; return 'done' status ret GSLogDrv endp Error proc near ; bad command code mov ax,8103h ; error bit and 'done' status ret ; and "Unknown Command" code Error endp MapES proc near ; map sector number to ; virtual memory address ; call with AX = logical sector ; return ES:DI = memory address ; and Carry clear ; or Carry set if error mov di,secsize ; bytes per sector mul di ; * logical sector number xchg ax,dx ; AX:BX := 32-bit physical mov bx,dx ; sector address add bx,word ptr dbase adc ax,word ptr dbase+2 mov cx,secsize mov dh,1 ; result to ES:DI mov dl,PhysToVirt ; function number call devhlp ; transfer to kernel ret ; return ES:DI = sector address MapES endp MapDS proc near ; map sector number to ; virtual memory address ; call with AX = logical sector ; return DS:SI = memory address ; and Carry clear ; or Carry set if error mov si,secsize ; bytes per sector mul si ; * logical sector number xchg ax,dx ; AX:BX := 32-bit physical mov bx,dx ; sector address add bx,word ptr dbase adc ax,word ptr dbase+2 mov cx,secsize mov dh,0 ; result to DS:SI mov dl,PhysToVirt ; function number call devhlp ; transfer to kernel ret ; return DS:SI = sector address MapDS endp Setup proc near ; set up for read/write ; ES:BX = request packet ; extracts address, start, count mov ax,es:[bx+14h] ; starting sector number mov xfrsec,ax mov ax,es:[bx+12h] ; sectors requested mov xfrreq,ax mov ax,es:[bx+0eh] ; requestor's buffer address mov word ptr xfraddr,ax mov ax,es:[bx+10h] mov word ptr xfraddr+2,ax mov xfrcnt,0 ; initialize sectors ; transferred counter ret Setup endp ; start of code discarded ; after initialization Init proc near ; function 0 = initialize mov ax,es:[bx+0eh] ; get DevHlp entry point mov word ptr devhlp,ax mov ax,es:[bx+10h] mov word ptr devhlp+2,ax mov al,es:[bx+16h] ; unit code for this drive add al,'A' ; convert to ASCII and place mov drive,al ; in output string ; display sign-on message... push stdout ; handle for standard output push ds ; address of message push offset DGROUP:ident push ident_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; set offsets to end of code ; and data segments mov word ptr es:[bx+0eh],offset _TEXT:Init mov word ptr es:[bx+10h],offset DGROUP:ident ; logical units supported mov byte ptr es:[bx+0dh],1 ; pointer to BPB array mov word ptr es:[bx+12h],offset DGROUP:array mov word ptr es:[bx+14h],ds push es ; save request packet address push bx ; allocate RAM disk storage mov bx,0 ; AX:BX = size of allocated mov ax,1 ; block in bytes mov dh,0 ; request block above 1 MB mov dl,AllocPhys ; function number call devhlp ; transfer to kernel jc Init9 ; jump if allocation failed ; save physical address ; of allocated block mov word ptr dbase,bx mov word ptr dbase+2,ax call format ; format the RAM disk jc Init9 ; jump if format failed pop bx ; restore request packet address pop es mov ax,0100h ; return 'done' status ret Init9: ; abort driver installation pop bx ; restore request packet address pop es ; display installation aborted... push stdout ; handle for standard output push ds ; address of message push offset DGROUP:abort push abort_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; set zero units mov byte ptr es:[bx+0dh],0 ; set lengths of code and ; data segments mov word ptr es:[bx+0eh],0 mov word ptr es:[bx+10h],0 mov ax,810ch ; return error and done and ; general failure ret Init endp Format proc near ; format the RAM disk area ; calculate length of ; RAM disk control areas mov al,byte ptr bpb+5 cbw ; number of FATs mov cx,bpb+0bh ; * sectors per FAT mul cx mov cx,bpb+6 ; entries in root directory shr cx,4 ; divide by 16 for sectors add ax,cx ; (FAT + dir sectors add ax,bpb+3 ; + reserved sectors) mov cx,secsize ; * bytes/sector mul cx ; = total length mov cx,ax ; convert RAM disk base to ; virtual address mov bx,word ptr dbase mov ax,word ptr dbase+2 mov dh,1 ; leave result in ES:DI mov dl,PhysToVirt ; function number call devhlp ; transfer to kernel jc Format9 ; jump if error xor ax,ax ; now zero control areas cld ; (assume CX still = length) rep stosb mov ax,0 ; get address of logical call MapES ; sector zero jc Format9 ; jump if error mov si,offset DGROUP:bootrec mov cx,bootrec_len ; copy boot record rep movsb ; to logical sector zero mov ax,word ptr bpb+3 call MapES ; get address of FAT sector jc Format9 ; jump if error mov al,byte ptr bpb+0ah mov es:[di],al ; medium ID byte into FAT mov word ptr es:[di+1],-1 mov ax,word ptr bpb+3 add ax,word ptr bpb+0bh call MapES ; get address of directory jc Format9 ; jump if error mov si,offset DGROUP:volname mov cx,volname_len rep movsb ; copy volume label to it mov dl,UnPhysToVirt ; function number call devhlp ; transfer to kernel clc ; signal format successful Format9: ; return with Carry = 0 ret ; if success, 1 if error Format endp _TEXT ends end Figure 17-31. TINYDISK.ASM, the source code for TINYDISK.SYS, an installable OS/2 RAM disk driver. LIBRARY TINYDISK PROTMODE Figure 17-32. TINYDISK.DEF, the module definition file used during linking of TINYDISK.SYS. At initialization time, TINYDISK allocates itself 64 KB of fixed memory above the 1 MB boundary for use as RAM disk storage. It then initializes the RAM disk's control areas by clearing the FAT and directory, copying a simulated boot block to the RAM disk's "logical sector 0," placing the fixed disk medium ID byte at the beginning of the FAT, and copying a simulated volume label to the start of the directory. TINYDISK then displays a sign-on message that includes its drive code. When TINYDISK receives a Read or Write request packet from the kernel, it simply translates the sector number into an offset within the RAM disk storage area, calls PhysToVirt to obtain virtual addresses appropriate for the current mode, and copies data directly between the application's buffer and the RAM disk memory. To keep the sample source code to a tolerable length, other request packets (such as Build BPB, Media Check, and Generic IOCtl) are handled in an adequate but simplistic fashion. Note especially the sequence of events in the TINYDISK Read and Write routines. Both routines convert the address of the RAM disk storage buffer first because it always lies above the 1 MB boundary and is guaranteed to cause a switch from real mode to protected mode if a switch is going to occur at all. The segment register that points to DGROUP is not saved on the stack until after the first call to PhysToVirt because the value in the register might change as a side effect of a mode switch. The code in these routines illustrates on a small scale the extreme care with which addressability must be handled in a full-fledged driver. TINYDISK.SYS is built from the source file TINYDISK.ASM and the module definition file TINYDISK.DEF with the following commands: [C:\] MASM TINYDISK.ASM; [C:\] LINK TINYDISK,TINYDISK.SYS,,OS2,TINYDISK After assembling and linking the driver, copy the file TINYDISK.SYS to the root directory of your boot disk, add the line DEVICE=TINYDISK.SYS to your CONFIG.SYS file, and restart the system. During system initialization you will see a line such as the following: TINYDISK 64 KB RAM disk for OS/2 RAM disk will be drive F: You can then copy files to and from the TINYDISK drive as though it were a small but very fast fixed disk. If TINYDISK cannot install itself because memory proves insufficient or because another system error occurs, it issues the following error message: TINYDISK installation aborted Chapter 18 Device Monitors Ordinarily, the flow of data between a physical character device and an application program follows a well-defined path through the system (Figure 18-1). By the time a character reaches an application program, it has been filtered, buffered, and possibly translated in two or more layers of system software. The unprocessed data that arrives from the device is hidden from normal applications by the privilege levels and protection mechanisms of the 80286 protected mode. Ŀ Presentation Manager application  Ŀ Ŀ Kernel OS/2 Presentation application Manager   Ŀ OS/2 kernel  Ŀ Device driver  Ŀ Physical device Figure 18-1. Normal flow of data between a character device and an application. To let normal programs intercept characters at a low level without running afoul of protected mode restrictions, OS/2 recognizes a class of applications called device monitors, for which it exports a special set of API support functions (Figure 18-2). In effect, these API functions allow an application to insert a probe into a device driver's raw data stream. A device monitor can add, consume, or remap characters or "events" before they pass from the driver to the physical device or from the driver to the kernel or its subsystems for distribution to applications. Function Description DosMonOpen Creates connection between device monitor and character device driver DosMonReg Inserts application buffers into monitor buffer chain DosMonRead Obtains data packet from monitor chain DosMonWrite Propels data packet into monitor chain DosMonClose Terminates connection between device monitor and character device driver Figure 18-2. OS/2 device monitor functions at a glance. Print spoolers, keyboard enhancers, and similar utilities can easily be written as device monitors, eliminating the need for custom device drivers in many cases. Furthermore, multiple applications can register as monitors for the same device and can even specify where they want to be in the chain of all monitors. Similarly, a single monitor can register with multiple device drivers or register multiple times with the same driver. Because monitors (like other applications) run at the lowest privilege level, and because the usual memory protection mechanisms are always in effect, the chances of harmful or unexpected interactions between monitors are minimal. As you might expect, OS/2's support for monitors involves an elaborate dialogue between the application, kernel, and device driver and requires a high degree of cooperation among the three to preserve the system's performance. A special kernel module, called the monitor dispatcher, arbitrates among the three components and moves data from the driver through the buffers of one or more monitors and back to the driver (Figure 18-3). Ŀ Presentation Manager application  Ŀ Ŀ Ŀ Kernel OS/2 Presentation Device application Manager monitor    Ŀ OS/2 kernel Monitor dispatcher  Ŀ Device  driver  Ŀ Physical device Figure 18-3. Flow of data between a character device and an application when a device monitor is registered. A monitor runs as a normal "user" process and has access to all the usual OS/2 API services. The characters are passed in packets from the driver to the device monitor, which can translate, delete, or add characters. The characters are then passed back to the driver, and from there they flow through the operating system to application programs or from the driver to the device. OS/2's default KBD$, MOUSE$, and PRN drivers all contain the necessary logic to support device monitors; its COMx and SCREEN$ drivers do not. Monitor support in other character drivers is optional. If you are using a custom device driver from a software or hardware vendor other than Microsoft, refer to the documentation for that driver to determine whether it supports monitors and, if so, what format its monitor data packets assume. An application can determine at run time whether a particular driver supports monitors by calling DosDevIOCtl Category 11 Function 60H (Query Monitor Support). Device Monitor Organization Device monitoring is restricted to protected mode applications; it is not available to Family or real mode programs. To register as a device monitor, an application first calls the DosMonOpen function with the logical name of the character device you want it to tap. If the driver for that device supports monitors, DosMonOpen returns a monitor handle. The application then calls DosMonReg with the monitor handle and the addresses of two data buffers that will be used for input and output of device driver data packets. A suitable buffer must be at least 20 bytes longer than the monitor data packet used by the character device driver of interest. Additional parameters to the DosMonReg call are an index (which identifies the screen group for keyboard and mouse monitors) and a code that specifies the location you prefer for this program in the chain of all monitors attached to this particular driver: first, last, or no preference. If additional programs later request the first (or last) position, the first program to make the request retains first (or last) position in the chain, the second program comes next, and so on. If the DosMonReg call succeeds, the program has successfully registered as a device monitor and becomes responsible for passing the driver's data through its monitor buffers without impeding the performance of the driver. To do so, it calls DosMonRead to obtain a data packet from the monitor chain and then DosMonWrite to return a packet to the chain. Typically, the program dedicates a thread with time-critical priority to a tight loop that performs no API calls except for DosSemSet, DosSemClear, and successive invocations of DosMonRead and DosMonWrite, so that other I/O operations or lengthy calculations by the monitor do not interfere with the driver's throughput. Between each DosMonRead and DosMonWrite, the monitor can inspect the character data. It can translate characters by modifying the packets, consume characters by simply not calling DosMonWrite, or add characters to the data stream by constructing additional packets in the existing format and calling DosMonWrite to shove them into the monitor chain. When the program wants to unhook from the character stream it is monitoring, it calls DosMonClose with the monitor handle it received from the original DosMonOpen. The program can then release its other resources and terminate without any untoward effects. Figure 18-4 shows a skeleton of the code you would use in a monitor. The format of a monitor data packet is unique to the character device driver. The packet usually contains a date and time stamp, a byte of monitor flags, and one or more characters. Figures 18-5 through 18-8 on pp. 42527 show the structure of the data packets used by OS/2's KBD$, MOUSE$, and PRN drivers. KbdName db 'KBD$',0 ; Kbd logical device name KbdHan dw ? ; handle from DosMonOpen ; buffers for Kbd monitor KbdIn dw 128,64 dup (0) ; monitor input buffer KbdOut dw 128,64 dup (0) ; monitor output buffer KbdPkt db 128 dup (0) ; receives Kbd data packet KbdPLen dw ? ; length of data in KbdPkt ScrGrp dw ? ; current screen group . . . ; open monitor connection... push ds ; address of device name push offset DGROUP:KbdName push ds ; receives monitor handle push offset DGROUP:KbdHan call DosMonOpen ; transfer to OS/2 or ax,ax ; was open successful? jnz error ; jump if function failed ; register Kbd monitor... push KbdHan ; handle from DosMonOpen push ds ; input buffer address push offset DGROUP:KbdIn push ds ; output buffer address push offset DGROUP:KbdOut push 1 ; request front of list push ScrGrp ; push screen group number call DosMonReg ; transfer to OS/2 or ax,ax ; registration successful? jnz error ; jump if function failed . . . next: ; this loop is the actual ; keyboard monitor... ; set max length for read mov KbdPLen,KbdPLen-KbdPkt ; get next Kbd packet... push ds ; address of input buffer push offset DGROUP:KbdIn push 0 ; wait until data available push ds ; receives data packet push offset DGROUP:KbdPkt push ds ; receives packet length push offset DGROUP:KbdPLen call DosMonRead ; transfer to OS/2 or ax,ax ; was read successful? jnz error ; jump if function failed ; is the key our hot key? cmp byte ptr KbdPkt+3,HotKey jz keyact ; jump if it is ; not hot key, pass it on... push ds ; output buffer address push offset DGROUP:KbdOut push ds ; contains data packet push offset DGROUP:KbdPkt push KbdPLen ; contains packet length call DosMonWrite ; transfer to OS/2 or ax,ax ; was write successful? jnz error ; jump if function failed jmp next ; wait for another key . . . done: ; cease monitoring... push KbdHan ; monitor handle call DosMonClose ; transfer to OS/2 or ax,ax ; was close successful? jnz error ; jump if function failed . . . Figure 18-4. Skeleton of application code related to function as a keyboard device monitor. 00H Ŀ Bit(s) Significance (if set) 0 Open packet (not used by KBD$ monitors) 1 Close packet (not used by KBD$ monitors) 2 Flush packet 3-7 ReservedĴ Monitor flags 01H Ĵ Original keyboard scan code, as read from the hardware. Monitors pass this field untouched and place zero here in packets they add to the data stream.Ĵ Original scan code 02H Ĵ Translated ASCII character code, derived from shift state and translated scan codeĴ Translated character 03H Ĵ Translated scan code (may or may not be same as byte 01H)Ĵ Translated scan code 04H Ĵ DBCS status Bit(s) Significance (if set) 0 Shift status returned without character 1-4 Reserved (0) 5 On-the-spot character conversion requested 6-7 Interim/final character status 00 Undefined 01 Final character, interim character flag off 10 Interim character 11 Final character, interim character flag on 05H Ĵ DBCS shift DBCS (double-byte character set) shift status (currently reserved, always zero) 06H Ĵ Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 ScrollLock on 5 NumLock on 6 CapsLock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 ScrollLock key down 13 NumLock key down 14 CapsLock key down 15 SysReq key downĴ Shift state 08H Ĵ Time stamp Time stamp: msec. elapsed since system was turned on or restarted 0CH Ĵ Keyboard driver flag word Bit(s) Significance (if set) 0-5 Special action field for keyboard driver, filled during translation of the scan code; always zero in OS/2 1.0 and 1.1 (character is simply placed in keyboard input buffer) 6 Record generated by key release 7 Secondary key on enhanced keyboard 8 "Multimake"; scan code was generated as a typematic repeat of a toggle key or a shift key 9 Key was translated using the previous key passed, which was an accent key 10-13 Reserved (0) 14-15 Available for communication between keyboard monitors, not used by keyboard driver 0EN Figure 18-5. Monitor data packet for the KBD$ driver. Note that if bit 6 of teh status byte is clear, no character was returned, and the record may reflect changed shift state. 00H Ŀ Monitor flags Bit(s) Significance (if set) 0 Open packet 1 Close packet 2 Flush packet 3-7 Reserved 01H Ĵ Reserved 02H Ĵ Mouse event flags Bit(s) Significance (if set) 0 Pointer position changed 1 Leftmost button pressed 2 Leftmost button released 3 Rightmost button pressed 4 Rightmost button released 5 Center button pressed 6 Center button released 7-15 Reserved 04H Ĵ Mouse event time stamp Msec. elapsed since system was turned on or restarted 08H Ĵ y coordinate Row of mouse pointer, assuming (x,y) = (0,0) at upper left corner of screen 0AH Ĵ x coordinate Column of mouse pointer 0CH Figure 18-6. Monitor data packet for the MOUSE$ driver. 00H Ŀ Monitor flags Bit(s) Significance (if set) 0 Open packet 1 Close packet 2 Flush packet 3-7 Reserved 01H Ĵ Printer driver flags Bit(s) Significance (if set) 0-2 Data packet 3-7 Reserved 02H Ĵ Process ID PID for the application which originated the data 04H Ĵ Character data (up to 128 bytes) Figure 18-7. Monitor character data packet for the PRN driver (maximum length 132 bytes). The application determines the length of the packet from the value returned by DosMonRead. 00H Ŀ Bit(s) Significance (if set) 0 Open Packet 1 Close packet 2 Flush packet 3-7 ReservedĴ Monitor flags 01H Ĵ Bit(s) Significance (if set) 0 Font monitor buffer command 1 Code page command was processed by spooler 2 Int 17H code page request 3 Status packet 4-7 ReservedĴ Printer driver flags 02H Ĵ Process ID PID for the application that originated the data 04H Ĵ Command byte Value Significance 01H Activate font 02H Query active font 03H Verify font 05H Ĵ Reserved (0) 08H Ĵ Code Meaning 0000H Successful completion FE02H Code page not available FE03H Code page function not active (spooler not started) FE04H Font ID not available FE13H Error reading font file control sequence section FE15H Error reading font file definition blockĴ Return code area 0AH Ĵ Code page Value Significance 0000H If both code page and font ID are zero, indicates hardware default code page and font 0001H- Specific code page FFFFH 0CH Ĵ Font ID Value Significance 0000H If both code page and font ID are zero, indicates hardware default code page and font 0000H If font ID is zero but code page is nonzero, indicates any font within the specified code page is acceptable 0001H- Specific font ID FFFFH 0EH Figure 18-8. Monitor code page packet for the PRN driver. Device Driver Support for Monitors A driver informs the kernel that it is prepared to support monitors by issuing the DevHlp call MonitorCreate, which establishes an initial "empty chain" of device monitors and assigns a monitor handle to it. The driver can create the monitor chain during its initialization, or it can wait until the chain is needed. When an application executes DosMonOpen, the driver receives a special Device Open request packet that has bit 3 of the status word set. At this point, the driver must create the empty monitor chain if it hasn't already done so. The driver is informed that an application has issued DosMonReg by the receipt of a Generic IOCtl Category 10 Function 40H request packet; it then calls the DevHlp service Register, which causes the kernel's monitor dispatcher to insert the application's buffers into the monitor chain at the appropriate point. The driver can identify the requesting process by getting the selector for the local information segment with the DevHlp GetDOSVar and then extracting the PID from that segment. After it creates the monitor chain (whether any monitors have registered or not), the driver must pass each character it receives from the device through the chain by calling the DevHlp service MonWrite. The kernel's monitor dispatcher calls a notification routine within the driver when data emerges from the end of the monitor chain. For the mouse and keyboard drivers, the data is then passed onward to the Kbd or Mou subsystems in the usual fashion. In the case of the printer driver, the data is then sent to the parallel port. When an application issues DosMonClose, the driver receives a special Device Close request packet with bit 3 of the status word set. The driver can obtain the PID of the current process by the procedure previously described and then call the DevHlp service DeRegister to remove the application's buffers from the monitor chain. When the driver notes that all monitor connections have been closed, it can call the DevHlp MonitorCreate again with a parameter that causes the monitor chain to be destroyed. Sample Device Monitor: SNAP The listings SNAP.ASM (Figure 18-9) and SNAP.C (Figure 18-10 on page 445) contain the source code for SNAP.EXE, a sample OS/2 device monitor. After you launch SNAP from the command prompt, it registers itself as a monitor with the keyboard driver (KBD$) for the current screen group. It then watches the keyboard data stream and captures the current screen display into a disk file whenever it detects a particular hot key. The file is named SNAPxx.IMG (where xx is the current number of the current screen group) and is always located in the root directory of the drive that was current when SNAP was loaded. The user can capture multiple screen dumps into the same file; SNAP separates them with divider lines. If a file with the same name exists when SNAP is loaded, it will be overwritten with new dump data. Although the SNAP program is relatively short, it is fairly complex and illustrates many of the OS/2 programming topics that have been discussed in this book: Reading and writing the video display with the Vio subsystem calls Creating, opening, and updating disk files Generating tones with DosBeep Creating and using RAM and system semaphores for intraprocess and interprocess signaling Creating multiple threads within a process and controlling one thread with another Spawning a child process Registering and deregistering a keyboard monitor and filtering the keyboard data stream for hot keys title SNAP -- Sample OS/2 Device Monitor page 55,132 .286 ; ; SNAP.ASM ; ; A sample OS/2 device monitor that captures the current display into ; the file SNAPxx.IMG, where xx is the session number. SNAP works in ; character mode only and may not be used in a PM window. The ; following keys are defined as defaults: ; ; Alt-F10 hot key to capture a screen ; Ctrl-F10 hot key to deinstall SNAP.EXE ; ; Assemble with: C> masm snap.asm; ; Link with: C> link snap,,,os2,snap ; ; Usage is: C> snap ; ; Copyright (C) 1988 Ray Duncan ; cr equ 0dh ; ASCII character codes lf equ 0ah ; hot key definitions: snapkey equ 71h ; snapshot Alt-F10 exitkey equ 67h ; exit Ctrl-F10 stksize equ 2048 ; stack size for threads extrn DosAllocSeg:far extrn DosBeep:far extrn DosBufReset:far extrn DosClose:far extrn DosCloseSem:far extrn DosCreateSem:far extrn DosCreateThread:far extrn DosExecPgm:far extrn DosExit:far extrn DosGetInfoSeg:far extrn DosOpenSem:far extrn DosMonClose:far extrn DosMonOpen:far extrn DosMonRead:far extrn DosMonReg:far extrn DosMonWrite:far extrn DosOpen:far extrn DosSemClear:far extrn DosSemSet:far extrn DosSemWait:far extrn DosSetPrty:far extrn DosSleep:far extrn DosSuspendThread:far extrn DosWrite:far extrn VioEndPopUp:far extrn VioGetMode:far extrn VioPopUp:far extrn VioReadCharStr:far extrn VioWrtCharStr:far jerr macro p1,p2,p3 ;; Macro to test return code local zero ;; in AX and jump if nonzero or ax,ax ;; Uses JMP DISP16 to avoid jz zero ;; branch out of range errors mov dx,offset DGROUP:p2 ;; p2 = message address mov cx,p3 ;; p3 = message length jmp p1 ;; routine p1 displays message zero: endm DGROUP group _DATA _DATA segment word public 'DATA' exitsem dd 0 ; semaphore for final exit snapsem dd 0 ; semaphore for 'snap' thread sname db '\SEM\SNAP' ; system semaphore name sname1 db 'nn.LCK',0 shandle dd 0 ; system semaphore handle pflags dw 0 ; VioPopUp flags wlen dw ? ; receives length written action dw ? ; receives DosOpen action watchID dw ? ; keyboard thread ID snapID dw ? ; snapshot thread ID sel dw ? ; selector from DosAllocSeg kname db 'KBD$',0 ; keyboard device name khandle dw 0 ; keyboard monitor handle fname db '\SNAP' ; name of snapshot file fname1 db 'nn.IMG',0 fhandle dw 0 ; handle for snapshot file scrbuf db 80 dup (0) ; receives screen data slen dw $-scrbuf ; length of screen buffer newline db cr,lf ; carriage return-linefeed nl_len equ $-newline gseg dw ? ; global info seg. selector lseg dw ? ; local info seg. selector obuff db 64 dup (0) ; receives name of dynlink obuff_len equ $-obuff ; causing DosExecPgm to fail kbdin dw 128,64 dup (0) ; input and output buffers kbdout dw 128,64 dup (0) ; for keyboard monitor kbdpkt db 128 dup (0) ; keyboard data packet kpktlen dw ? ; length of buffer/packet pname db 'SNAP.EXE',0 ; child process name retcode dd 0 ; child process info vioinfo label byte ; receives display mode dw 8 ; length of structure db 0 ; display mode type db 0 ; colors cols dw 0 ; number of columns rows dw 0 ; number of rows msg1 db 'SNAP utility installed!' msg1_len equ $-msg1 msg2 db 'Alt-F10 to capture screen into file SNAP.IMG,' msg2_len equ $-msg2 msg3 db 'Ctrl-F10 to shut down SNAP.' msg3_len equ $-msg3 msg4 db 'SNAP utility deactivated.' msg4_len equ $-msg4 msg5 db 'Error detected during SNAP installation:' msg5_len equ $-msg5 msg6 db 'Can''t create SNAP system semaphore.' msg6_len equ $-msg6 msg7 db 'Can''t start child copy of SNAP.' msg7_len equ $-msg7 msg8 db 'SNAP is already loaded.' msg8_len equ $-msg8 msg9 db 'Can''t open KBD$ monitor connection.' msg9_len equ $-msg9 msg10 db 'Can''t register as KBD$ monitor.' msg10_len equ $-msg10 msg11 db 'Can''t allocate thread stack.' msg11_len equ $-msg11 msg12 db 'Can''t create keyboard thread.' msg12_len equ $-msg12 msg13 db 'Can''t create snapshot thread.' msg13_len equ $-msg13 msg14 db 'Can''t create snapshot file.' msg14_len equ $-msg14 divider db 79 dup ('-'),cr,lf divider_len equ $-divider _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 ; get info segment selectors push ds ; receives global info selector push offset DGROUP:gseg push ds ; receives local info selector push offset DGROUP:lseg call DosGetInfoSeg ; transfer to OS/2 ; build system semaphore ; and snapshot file names mov es,gseg ; get foreground screen group mov al,es:[0018h] aam ; convert to ASCII add ax,'00' xchg ah,al mov word ptr fname1,ax ; store into filename mov word ptr sname1,ax ; store into semaphore name ; does SNAPxx.LCK exist? push ds ; receives semaphore handle push offset DGROUP:shandle push ds push offset DGROUP:sname ; semaphore name call DosOpenSem ; transfer to OS/2 or ax,ax ; was open successful? jz main1 ; jump, we're child SNAP ; we're the parent SNAP, ; create system semaphore push 1 ; make it nonexclusive push ds ; receives semaphore handle push offset DGROUP:shandle push ds ; system semaphore name push offset DGROUP:sname call DosCreateSem ; transfer to OS/2 jerr error,msg6,msg6_len ; jump if create failed ; set the semaphore... push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosSemSet ; transfer to OS/2 ; launch child SNAP... push ds ; object name buffer push offset DGROUP:obuff ; receives failed dynlink push obuff_len ; length of buffer push 4 ; child detached push 0 ; NULL argument pointer push 0 push 0 ; NULL environment pointer push 0 push ds ; receives child info push offset DGROUP:retcode push ds ; pathname for child push offset DGROUP:pname call DosExecPgm ; request launch of child jerr error,msg7,msg7_len ; jump if launch failed ; wait for child to load push word ptr shandle+2 ; semaphore handle push word ptr shandle push -1 ; timeout = indefinite push -1 call DosSemWait ; transfer to OS/2 ; close the semaphore... push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosCloseSem ; transfer to OS/2 jmp main3 ; now exit main1: ; come here if child SNAP... ; check if already resident push word ptr shandle+2 ; semaphore handle push word ptr shandle push 0 ; timeout = 0 push 0 call DosSemWait ; transfer to OS/2 or ax,ax ; is semaphore clear? jnz main2 ; no, proceed ; yes, don't load again mov dx,offset DGROUP:msg8 ; address of warning message mov cx,msg8_len ; length of message jmp error ; display message and exit main2: ; initialize semaphores... push ds ; address of exit semaphore push offset DGROUP:exitsem call DosSemSet ; transfer to OS/2 push ds ; address of snapshot semaphore push offset DGROUP:snapsem call DosSemSet ; transfer to OS/2 ; open monitor connection ... push ds ; address of device name push offset DGROUP:kname push ds ; receives monitor handle push offset DGROUP:khandle call DosMonOpen ; transfer to OS/2 jerr error,msg9,msg9_len ; jump if open failed ; register as keyboard monitor push khandle ; handle from DosMonOpen push ds ; monitor input buffer address push offset DGROUP:kbdin push ds ; monitor output buffer address push offset DGROUP:kbdout push 1 ; position = front of list mov es,gseg ; foreground session number mov al,byte ptr es:[0018h] ; from global info segment xor ah,ah push ax call DosMonReg ; transfer to OS/2 jerr error,msg10,msg10_len ; jump if register failed push stksize ; alloc. stack for WATCH thread push ds ; variable to receive selector push offset DGROUP:sel push 0 ; not shareable call DosAllocSeg ; transfer to OS/2 jerr error,msg11,msg11_len ; jump, can't allocate stack ; create keyboard thread push cs ; initial execution address push offset _TEXT:watch push ds ; receives thread ID push offset DGROUP:watchID push sel ; address of thread's stack push stksize call DosCreateThread ; transfer to OS/2 jerr error,msg12,msg12_len ; jump, can't create thread ; promote keyboard thread push 2 ; scope = single thread push 3 ; class = time critical push 0 ; delta = 0 push watchID ; thread ID call DosSetPrty ; transfer to OS/2 push stksize ; allocate stack for SNAP thread push ds ; variable to receive selector push offset DGROUP:sel push 0 ; not shareable call DosAllocSeg ; transfer to OS/2 jerr error,msg11,msg11_len ; jump, can't allocate stack ; create snapshot thread push cs ; initial execution address push offset _TEXT:snap push ds ; receives thread ID push offset DGROUP:snapID push sel ; address of thread's stack push stksize call DosCreateThread ; transfer to OS/2 jerr error,msg13,msg13_len ; jump, can't create thread call signon ; announce installation ; tell parent we are running push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosSemClear ; transfer to OS/2 ; block on exit semaphore... push ds ; semaphore handle push offset DGROUP:exitsem push -1 ; timeout = indefinite push -1 call DosSemWait ; transfer to OS/2 push watchID ; suspend keyboard thread call DosSuspendThread ; transfer to OS/2 push snapID ; suspend snapshot thread call DosSuspendThread ; transfer to OS/2 ; close monitor connection push khandle ; monitor handle call DosMonClose ; transfer to OS/2 ; close system semaphore push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosCloseSem ; transfer to OS/2 ; close snapshot file push fhandle ; file handle call DosClose ; transfer to OS/2 call signoff ; announce deinstallation main3: push 1 ; terminate all threads push 0 ; return success code call DosExit ; final exit to OS/2 main endp error proc near ; fatal error encountered ; DS:DX = message, CX = length test khandle,-1 ; monitor active? jz error1 ; no, jump ; yes, shut it down push khandle ; monitor handle call DosMonClose ; transfer to OS/2 error1: mov ax,word ptr shandle ; system semaphore open? or ax,word ptr shandle+2 jz error2 ; no, jump ; clear semaphore, in case ; we're the child SNAP push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosSemClear ; transfer to OS/2 ; close the semaphore push word ptr shandle+2 ; semaphore handle push word ptr shandle call DosCloseSem ; transfer to OS/2 error2: mov ax,1 ; get popup window call popup ; display title... push ds ; message address push offset DGROUP:msg5 push msg5_len ; message length push 10 ; Y push (80-msg5_len)/2 ; X (center it) push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 ; display error message... push ds ; message address push dx push cx ; message length push 12 ; Y mov ax,80 ; X (center it) sub ax,cx shr ax,1 push ax push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 push 0 ; pause for 3 seconds push 3000 call DosSleep ; transfer to OS/2 call unpop ; release popup window push 1 ; terminate all threads push 1 ; return error code call DosExit ; exit program error endp watch proc far ; keyboard thread, monitors ; for snapshot or exit hot keys mov kpktlen,kpktlen-kbdpkt ; max buffer length for read ; get keyboard data packet... push ds ; monitor input buffer address push offset DGROUP:kbdin push 0 ; wait until data available push ds push offset DGROUP:kbdpkt ; receives keyboard data packet push ds push offset DGROUP:kpktlen ; contains/receives length call DosMonRead ; transfer to OS/2 cmp kbdpkt+2,0 ; is this extended code? jnz watch1 ; no, pass it on cmp kbdpkt+3,exitkey ; is it exit hot key? jz watch2 ; jump if exit key cmp kbdpkt+3,snapkey ; is it snapshot hot key? jnz watch1 ; no, jump cmp word ptr kbdpkt+12,0 ; is it break packet? jnz watch ; yes, ignore it ; snapshot hot key detected ; clear snapshot semaphore... push ds ; semaphore handle push offset DGROUP:snapsem call DosSemClear ; transfer to OS/2 jmp watch ; discard this hot key watch1: ; not hot key, pass character push ds ; monitor output buffer address push offset DGROUP:kbdout push ds ; keyboard data packet address push offset DGROUP:kbdpkt push kpktlen ; length of data packet call DosMonWrite ; transfer to OS/2 jmp watch ; get another packet watch2: ; exit hot key detected... cmp word ptr kbdpkt+12,0 ; is it break packet? jnz watch ; yes, ignore it ; clear exit semaphore... push ds ; semaphore handle push offset DGROUP:exitsem call DosSemClear ; transfer to OS/2 jmp watch ; let thread 1 shut down watch endp snap proc far ; This thread blocks on the ; snapshot semaphore, then ; dumps the screen contents ; to the file SNAPxx.IMG. ; open/create snapshot file push ds ; address of filename push offset DGROUP:fname push ds ; variable to receive file handle push offset DGROUP:fhandle push ds ; variable to receive action taken push offset DGROUP:action push 0 ; initial file size push 0 push 0 ; normal file attribute push 12h ; create or replace file push 21h ; write access, deny write push 0 ; DWORD reserved push 0 call DosOpen ; transfer to OS/2 jerr error,msg14,msg14_len ; jump if can't create snap1: ; write divider line push fhandle ; file handle push ds ; address of divider string push offset DGROUP:divider push divider_len ; length of string push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; force disk update... push fhandle ; file handle call DosBufReset ; transfer to OS/2 ; wait on snapshot semaphore push ds ; semaphore handle push offset DGROUP:snapsem push -1 ; timeout = indefinite push -1 call DosSemWait ; transfer to OS/2 mov ax,3 ; pop-up in transparent mode call popup ; to read screen contents ; get screen dimensions... push ds ; receives video mode info push offset DGROUP:vioinfo push 0 ; Vio handle call VioGetMode ; transfer to OS/2 mov bx,0 ; BX := initial screen row snap2: ; read line from screen... mov ax,cols ; width to read mov slen,ax push ds ; address of screen buffer push offset DGROUP:scrbuf push ds ; contains/receives length push offset DGROUP:slen push bx ; screen row push 0 ; screen column push 0 ; Vio handle call VioReadCharStr ; transfer to OS/2 push ds ; scan backwards from end pop es ; of line to find last mov cx,slen ; nonblank character mov di,offset DGROUP:scrbuf add di,slen dec di mov al,20h std repe scasb cld jz snap3 ; if Z = True, line was empty inc cx ; otherwise correct the length snap3: ; write line to file... push fhandle ; file handle push ds ; address of data push offset DGROUP:scrbuf push cx ; clipped line length push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; write newline (CR-LF) push fhandle ; file handle push ds push offset DGROUP:newline ; address of newline push nl_len ; length of newline push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 inc bx ; bump screen row counter cmp bx,rows ; whole screen done yet? jne snap2 ; no, write another push 440 ; reward user with some push 200 ; audible feedback call DosBeep ; transfer to OS/2 call unpop ; release the screen ; done with screen capture, ; reset snapshot semaphore push ds ; semaphore handle push offset DGROUP:snapsem call DosSemSet ; transfer to OS/2 jmp snap1 ; go wait on semaphore snap endp signon proc near ; announce installation, ; display help message mov ax,1 ; put up popup window call popup ; mode = wait, nontransparent push ds ; message address push offset DGROUP:msg1 push msg1_len ; message length push 10 ; Y push (80-msg1_len)/2 ; X (center it) push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 push ds ; message address push offset DGROUP:msg2 push msg2_len ; message length push 13 ; Y push (80-msg2_len)/2 ; X (center it) push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 push ds ; message address push offset DGROUP:msg3 push msg3_len ; message length push 15 ; Y push (80-msg3_len)/2 ; X (center it) push 0 ; Vio handle call VioWrtCharStr ; transfer to OS/2 push 0 ; pause for 4 seconds push 4000 ; so user can read message call DosSleep ; transfer to OS/2 call unpop ; take down popup window ret ; back to caller signon endp signoff proc near ; announce deinstallation mov ax,1 ; put up popup window call popup ; mode = wait, nontransparent push ds ; message address push offset DGROUP:msg4 push msg4_len ; message length push 12 ; Y push (80-msg4_len)/2 ; X (center it) push 0 ; Vio handle call VioWrtCharStr push 0 ; pause for 2 seconds push 2000 ; so user can read message call DosSleep ; transfer to OS/2 call unpop ; take down popup window ret signoff endp popup proc near ; put up popup window ; AX = VioPopUp flags ; bit 0 = 0 no wait ; 1 wait for pop-up ; bit 1 = 0 nontransparent ; 1 transparent mov pflags,ax ; set pop-up mode push ds ; address of pop-up flags push offset DGROUP:pflags push 0 ; Vio handle call VioPopUp ; transfer to OS/2 ret ; back to caller popup endp unpop proc near ; take down popup window push 0 ; Vio handle call VioEndPopUp ; transfer to OS/2 ret ; back to caller unpop endp _TEXT ends end main Figure 18-9. SNAP.ASM, source code for SNAP.EXE sample device monitor. /* SNAP.C A sample OS/2 device monitor that captures the current display into the file SNAPxx.IMG, where xx is the session number. SNAP works in character mode only and and may not be used in a PM window. The following keys are defined as defaults: Alt-F10 hot key to capture a screen Ctrl-F10 hot key to deinstall SNAP.EXE Compile with: C> cl /F 2000 snap.c Usage is: C> snap Copyright (C) 1988 Ray Duncan */ #include #include /* hot key definitions */ #define SNAPKEY 0x71 /* Alt-F10 to capture screen */ #define EXITKEY 0x67 /* Ctrl-F10 to exit */ #define STKSIZE 2048 /* stack size for threads */ #define WAIT 0 /* parameters for DosMonRead */ #define NOWAIT 1 /* and DosMonWrite */ #define API unsigned extern far pascal /* API function prototypes */ API DosBeep(unsigned, unsigned); API DosBufReset(unsigned); API DosClose(unsigned); API DosCloseSem(unsigned long far *); API DosCreateThread(void (far *)(), unsigned far *, void far *); API DosCreateSem(unsigned, unsigned long far *, char far *); API DosExecPgm(char far *, int, int, char far *, char far *, int far *, char far *); API DosExit(unsigned, unsigned); API DosGetInfoSeg(unsigned far *, unsigned far *); API DosMonClose(unsigned); API DosMonOpen(char far *, unsigned far *); API DosMonRead(void far *, unsigned, char far *, int far *); API DosMonReg(unsigned, void far *, void far *, int, unsigned); API DosMonWrite(void far *, char far *, int); API DosOpen(char far *, unsigned far *, unsigned far *, unsigned long, unsigned, unsigned, unsigned, unsigned long); API DosOpenSem(unsigned long far *, char far *); API DosSemClear(unsigned long far *); API DosSemSet(unsigned long far *); API DosSemWait(unsigned long far *, unsigned long); API DosSetPrty(int, int, int, int); API DosSleep(unsigned long); API DosSuspendThread(unsigned); API DosWrite(unsigned, void far *, int, unsigned far *); API VioEndPopUp(unsigned); API VioGetMode(void far *, unsigned); API VioPopUp(unsigned far *, unsigned); API VioReadCharStr(char far *, int far *, int, int, unsigned); API VioWrtCharStr(char far *, int, int, int, unsigned); void signon(void); /* local function prototypes */ void signoff(void); void popup(unsigned); void unpop(void); void far snap(void); void far watch(void); void errexit(char *); /* RAM semaphores */ unsigned long exitsem = 0; /* exit hot key semaphore */ unsigned long snapsem = 0; /* screen snapshot semaphore */ char sname[20]; /* system semaphore name */ unsigned long shandle = 0; /* system semaphore handle */ char fname[20]; /* snapshot filename */ unsigned fhandle = 0; /* snapshot file handle */ char kname[] = "KBD$"; /* keyboard device name */ unsigned khandle = 0; /* keyboard monitor handle */ struct _monbuf { /* monitor input and */ int len; /* output buffers */ char buf[128]; } kbdin = { sizeof(kbdin.buf) } , kbdout = { sizeof(kbdout.buf) } ; struct _vioinfo { /* display mode info */ int len; char type; char colors; int cols; int rows; } vioinfo; char msg1[] = "SNAP utility installed!"; char msg2[] = "Alt-F10 to capture screen image into file SNAP.IMG,"; char msg3[] = "Ctrl-F10 to shut down SNAP."; char msg4[] = "SNAP utility deactivated."; char msg5[] = "Error detected during SNAP installation:"; main() { char obuff[80]; /* object name buffer */ int retcode[2]; /* receives child info */ unsigned gseg, lseg; /* receives selectors */ char far *ginfo; /* global info segment pointer */ unsigned snapID, watchID; /* receives thread IDs */ char snapstk[STKSIZE]; /* snapshot thread stack */ char watchstk[STKSIZE]; /* keyboard thread stack */ DosGetInfoSeg(&gseg, &lseg); /* get info segment selectors */ (long) ginfo = (long) gseg << 16; /* make far pointer */ /* build semaphore and filenames */ sprintf(sname, "\\SEM\\SNAP%02d.LCK", ginfo[0x18]); sprintf(fname, "\\SNAP%02d.IMG", ginfo[0x18]); if(DosOpenSem(&shandle, sname)) /* does SEMSNAPxx.LCK exist? */ { /* no, we're parent SNAP */ /* create system semaphore */ if(DosCreateSem(1, &shandle, sname)) errexit("Can't create SNAP system semaphore."); DosSemSet((unsigned long far *) shandle); /* start detached child SNAP */ if(DosExecPgm(obuff, sizeof(obuff), 4, NULL, NULL, retcode, "snap.exe")) errexit("Can't start child copy of SNAP."); /* wait for child to load */ DosSemWait((unsigned long far *) shandle, -1L); DosCloseSem((unsigned long far *) shandle); } else /* if SNAPxx.LCK exists, */ { /* we're the child SNAP */ /* abort if already resident */ if(! DosSemWait((unsigned long far *) shandle, 0L)) errexit("SNAP is already loaded."); DosSemSet(&exitsem); /* initialize exit and */ DosSemSet(&snapsem); /* snapshot semaphores */ if(DosMonOpen(kname, &khandle)) /* open monitor connection */ errexit("Can't open KBD$ monitor connection."); /* register at head of chain */ if(DosMonReg(khandle, &kbdin, &kbdout, 1, ginfo[0x18])) errexit("Can't register as KBD$ monitor."); /* create keyboard thread */ if(DosCreateThread(watch, &watchID, watchstk+STKSIZE)) errexit("Can't create keyboard thread."); DosSetPrty(2, 3, 0, watchID); /* promote keyboard thread */ /* create snapshot thread */ if(DosCreateThread(snap, &snapID, snapstk+STKSIZE)) errexit("Can't create snapshot thread."); signon(); /* announce installation */ /* tell parent we're running */ DosSemClear((unsigned long far *) shandle); DosSemWait(&exitsem, -1L); /* wait for exit hot key */ DosSuspendThread(snapID); /* suspend snapshot thread */ DosSuspendThread(watchID); /* suspend keyboard thread */ DosMonClose(khandle); /* close monitor connection */ /* close system semaphore */ DosCloseSem((unsigned long far *) shandle); DosClose(fhandle); /* close snapshot file */ signoff(); /* announce deinstallation */ } DosExit(1, 0); /* final exit */ } /* The 'watch' thread is responsible for monitoring the keyboard data stream. It clears the 'snapsem' semaphore when the screen capture hot key is detected and clears the 'exitsem' semaphore when the deinstall hot key is detected. */ void far watch(void) { char kbdpkt[128]; /* monitor data packet */ int kbdpktlen; /* data packet length */ while(1) { kbdpktlen = sizeof(kbdpkt); /* set buffer length */ /* read monitor data */ DosMonRead(&kbdin, WAIT, kbdpkt, &kbdpktlen); /* check for hot keys */ /* ignore key breaks */ if((kbdpkt[2] == 0) && (kbdpkt[3] == EXITKEY)) { /* exit hot key detected */ if(kbdpkt[12] == 0) DosSemClear(&exitsem); } else if((kbdpkt[2] == 0) && (kbdpkt[3] == SNAPKEY)) { /* snapshot hot key detected */ if(kbdpkt[12] == 0) DosSemClear(&snapsem); } /* not hot key, pass it through */ else DosMonWrite(&kbdout, kbdpkt, kbdpktlen); } } /* The 'snap' thread blocks on the 'snapsem' semaphore until it is cleared by the 'watch' thread, then captures the current screen contents into the snapshot file. */ void far snap(void) { int i; /* scratch variable */ unsigned action; /* receives DosOpen action */ unsigned wlen; /* receives DosWrite length */ char divider[81]; /* snapshot divider line */ char scrbuf[80]; /* receives screen data */ int slen; /* contains buffer size */ memset(divider, '-', 79); /* initialize divider line */ divider[79] = 0x0d; divider[80] = 0x0a; /* create/replace snapshot file */ if(DosOpen(fname, &fhandle, &action, 0L, 0, 0x12, 0x21, 0L)) errexit("Can't create snapshot file."); while(1) { /* write divider line */ DosWrite(fhandle, divider, sizeof(divider), &wlen); DosBufReset(fhandle); /* force file update */ DosSemWait(&snapsem, -1L); /* wait for hot key */ popup(3); /* pop-up in transparent mode */ vioinfo.len = sizeof(vioinfo); /* get screen dimensions */ VioGetMode(&vioinfo, 0); for(i = 0; i < vioinfo.rows; i++) { slen = vioinfo.cols; /* read line from screen */ VioReadCharStr(scrbuf, &slen, i, 0, 0); /* discard trailing spaces */ while((slen > 0) && (scrbuf[slen-1] == 0x20)) slen--; /* write line to file */ DosWrite(fhandle, scrbuf, slen, &wlen); DosWrite(fhandle, "\x0d\x0a", 2, &wlen); } DosBeep(440, 200); /* reward the user */ unpop(); /* release screen */ DosSemSet(&snapsem); /* reset snapshot semaphore */ } } /* Display the installation and help messages in popup window. */ void signon(void) { popup(1); /* acquire popup screen */ VioWrtCharStr(msg1, sizeof(msg1), 10, ((80-sizeof(msg1))/2), 0); VioWrtCharStr(msg2, sizeof(msg2), 13, ((80-sizeof(msg2))/2), 0); VioWrtCharStr(msg3, sizeof(msg3), 15, ((80-sizeof(msg3))/2), 0); DosSleep(4000L); /* pause for 4 seconds */ unpop(); /* release popup screen */ } /* Display exit message in popup window. */ void signoff(void) { popup(1); /* acquire popup screen */ VioWrtCharStr(msg4, sizeof(msg4), 12, ((80-sizeof(msg4))/2), 0); DosSleep(2000L); /* pause for 2 seconds */ unpop(); /* release popup screen */ } /* Get popup screen, using wait/no-wait and transparent/nontransparent flags supplied by caller. */ void popup(unsigned pflags) { VioPopUp(&pflags, 0); } /* Take down popup screen. */ void unpop(void) { VioEndPopUp(0); } /* Common error exit routine. Display error message on popup screen and terminate process. */ void errexit(char *errmsg) { if(khandle != 0) /* close monitor handle */ DosMonClose(khandle); /* if monitor active */ if(shandle != 0) /* clear and close the */ { /* SNAPxx.LCK semaphore */ DosSemClear((unsigned long far *) shandle); DosCloseSem((unsigned long far *) shandle); } popup(1); /* get popup screen and */ /* display error message */ VioWrtCharStr(msg5, sizeof(msg5), 10, ((80-sizeof(msg5))/2), 0); VioWrtCharStr(errmsg, strlen(errmsg), 12, ((80-strlen(errmsg))/2), 0); DosSleep(3000L); /* let user read message */ unpop(); /* release popup screen */ DosExit(1, 1); /* terminate, exitcode = 1 */ } Figure 18-10. SNAP.C, source code for SNAP.EXE sample device monitor. How SNAP Works The SNAP program contains three threads of execution, which communicate using RAM semaphores (Figure 18-11). main thread Ŀ Initialize semaphores Ŀ Register as KBD$ monitor Ŀ Create watch and snap threads watch thread snap thread Ŀ Ŀ Ŀ Block on exit DosMonRead Ŀ Create semaphore snapshot file .  . /\ . / \ Ŀ . / \ Block on . Yes / Hot key? \ No  snap Ŀ \ /Ŀ semaphore Suspend watch \ / and snap \ / . threads \/ . . Ŀ . Ŀ DosMonWrite  . Deregister as . KBD$ monitor Ŀ Ŀ Clear Copy screen appropriate to snapshot Ŀ semaphore file Close snapshot file Ŀ Terminate process Figure 18-11. General flow of control in the SNAP sample device monitor. The primary thread, called main, receives control from OS/2. It calls DosGetInfoSeg to get the selectors for the global and local information segments, extracts the screen group to be monitored from the global information segment, and registers itself as a keyboard monitor using DosMonOpen and DosMonReg. Next, it initializes two RAM semaphores that will be used for signaling: an exit semaphore and a screen snapshot semaphore. The main thread then activates two additional threads (watch and snap), displays the program's sign-on message, and blocks on the exit semaphore. The watch thread simply executes successive calls to DosMonRead and DosMonWrite to pump the keyboard data through its buffers, monitoring the characters that pass for either of the two SNAP hot keys. Use of a dedicated thread for this purpose ensures that keyboard response time is not degraded. If watch detects the screen dump hot key (Alt-F10), it clears the snapshot semaphore to wake up the snap thread. If it sees the deinstall hot key (Ctrl-F10), it triggers the exit semaphore to wake up the main thread. The snap thread creates the file SNAPxx.IMG and then blocks on the snapshot semaphore. Whenever the snapshot semaphore is cleared by the watch thread, the snap thread wakes up, copies the current screen display into SNAPxx.IMG, appends a divider line of hyphens, and forces an update of the file and its directory entry on disk. The snap thread then uses DosBeep to let the user know something happened, resets the snapshot semaphore, and goes back to sleep. The main thread resumes execution only when the watch thread clears the exit semaphore. Upon reawakening it suspends the other two threads and then deregisters the process as a keyboard monitor. Finally, it closes the SNAPxx.IMG file, briefly displays a popup message that the program is removing itself from memory, and then terminates the process. The initial portion of code for the main thread illustrates a useful OS/2 trick: placing a process in the background so that another command processor prompt is displayed, without requiring the user to start the program with the DETACH command. SNAP accomplishes this by emulating a UNIX/XENIX "fork" operation. When the main thread first gets control, it tests to see whether a system semaphore with the name \SEM\SNAPxx.LCK exists. If not, it creates and sets the semaphore and then loads and executes another copy of SNAP with the DosExecPgm option 4 for "asynchronous execution, discard return code." The main thread waits for the new child process to clear the system semaphore (indicating that the process has been successfully loaded), and then it terminates. If the main thread finds that the semaphore \SEM\SNAPxx.LCK already exists, it knows itself to be the child copy of SNAP. It clears the semaphore to notify the parent SNAP that it is running and then goes about its business of initializing its data structures and creating additional threads as already described. The child copy of SNAP is free to continue executing in the background; because it was spawned with option 4, CMD.EXE does not wait for it to terminate before issuing another prompt. Building SNAP To assemble the source file SNAP.ASM into the relocatable object module SNAP.OBJ, enter the following command: [C:\] MASM SNAP.ASM; To build the actual program SNAP.EXE, use the Linker to combine the file SNAP.OBJ, the module definition file SNAP.DEF (Figure 18-12), and the dynlink reference file OS2.LIB, as follows: [C:\] LINK SNAP,,,OS2,SNAP To compile the file SNAP.C into the executable file SNAP.EXE, type the following command: [C:\] CL /F 2000 SNAP.C The /F switch allocates an extra-large stack so that stacks for the second and third threads can be allocated out of the main stack. NAME SNAP NOTWINDOWCOMPAT PROTMODE STACKSIZE 4096 Figure 18-12. SNAP.DEF, module definition file for SNAP.EXE device monitor. Using SNAP You can load SNAP (when any protected mode screen group is selected) simply by entering its name: [C:\] SNAP A popup sign-on message appears briefly to let you know that SNAP is active and to remind you of the hot key assignments. That message is followed by a new prompt from the system's protected mode command interpreter (CMD.EXE). While you are running other programs, SNAP remains resident, waiting to act upon one of two special keycodes. You can request a screen dump at any time by pressing Alt-F10. (A brief tone tells you that SNAP accomplished its task.) To remove SNAP from memory, press Ctrl-F10. You can alter the key assignments by editing the equates at the beginning of SNAP.ASM and reassembling the program. SNAP is active only for the screen group in which it was loaded; it is not aware of keys that are entered when another screen group is brought to the foreground with the Task Manager. You can, however, load a copy of SNAP in each screen group in which you want to use it. The overhead of such additional copies is minuscule because OS/2 allows all the instantiations to share the same machine code segment. With some extra work, you could also modify SNAP so that a single copy registers itself as a device monitor for all the active screen groups in the system. Chapter 19 Dynamic Link Libraries Dynamic link libraries (also called dynlink libraries or DLLs) are essentially subroutine packages that are bound to an application at load time or during execution, rather than at link time. But such a terse definition conveys little of the generality and power of this concept. The following list suggests some of the significant benefits of dynlink libraries: Code and read-only data in dynlink libraries is shared invisibly among all the processes ("clients") that use the libraries, a provision which conserves physical memory and disk swap space. Code and data in dynlink libraries can be loaded on demand, so it does not occupy physical memory until it is needed. Dynlink library routines can be referenced symbolically (that is, by name), so a library and the applications that use it can be independently maintained and improved. Centralization of frequently used routines in dynlink libraries reduces the size of each application program file and conserves disk space. Dynlink libraries can contain IOPL segments that enable and disable interrupts and access I/O ports directly. I/O routines for devices that do not require interrupt handlers can be implemented in dynlink libraries rather than in true device drivers; the result, as evidenced by the Vio subsystem, is a much more flexible interface to applications than that provided by a true device driver. Dynamic link libraries were pioneered and proven in real mode Windows, an environment that makes the most of a severely limited address space with a complex software emulation of virtual memory. But in OS/2, which can rely on hardware support for virtual memory and memory protection, dynlink libraries truly come into their own. Dynlink library segments reside in the global memory pool and can be freely moved, swapped, discarded, and reloaded on the basis of overall system activity. In fact, OS/2's dynlink library support can be seen as a general-purpose overlay facility, fully integrated with the system's linker, loader, and virtual memory manager. Much of OS/2 itself, including most API entry points and the code for API functions that do not need to run at ring 0, is distributed in the form of dynlink libraries (see Chapter 2). This rather elegant merger of OS/2's services and its implementation keeps the system's fixed memory requirements to a minimum. The routines in a dynlink library execute in user mode in the context of the client process. In other words, as far as the kernel's scheduler is concerned, a thread executing within a process is no different from a thread within a dynlink library bound to the process. No stack switching is involved; the library is viewed as an extension of the process. Consequently, the routines in a dynlink library have unrestricted access to the calling process's memory and its other resources, such as file, pipe, semaphore, and queue handles. From the point of view of an application, however, every dynlink library (regardless of its origin) is indistinguishable from a component of the operating system: Each DLL routine accepts its parameters on the stack, is entered by a far call to a named entry point, and returns a status code in AX. Details as to the way a DLL routine does its work are invisible to the application; within the DLL, the routine might continue to execute in user mode, invoke an IOPL segment, request action by the kernel on behalf of the application, or use IPC mechanisms to request services from a server process on the same or another machine. The system loader has yet another perspective. To the loader, every segmented executable file, whether it contains a DLL or an application, is simply another system resource called a module The term module as it is used in this chapter should not be confused with object modules, source code modules, or the like. . The module name is contained in the file header and may or may not be the same as the name of the file. While a program is running or a dynlink library is in use, the segment and entry point tables from the EXE file header are kept resident so that the loader can easily locate discardable segments and LOADONCALL segments when they are needed. If an additional copy of the same program is started, or if another process references the same library, the loader already has the necessary information in hand so that the system overhead for creating the new process or dynamic link is relatively small. The loader maintains a use count for each module. When all the instances of a program have terminated, or when all the clients of a dynlink library have released it or terminated, the module use count becomes zero, and the loader discards the module and associated tables and reclaims the memory. This behavior of the loader has an interesting side effect. Any time that a program is running or that a dynlink library is in use, the file containing that module is held open by the loader with a sharing mode of deny-write; during this time, the file cannot be renamed, erased, or replaced. (This prohibition improves performance and reliability by eliminating multiple open/close operations and ensuring that a discardable segment cannot be modified between loads.) Because the operating system's base DLLs (such as DOSCALL1.DLL and VIOCALLS.DLL) are always in use by the Presentation Manager and CMD.EXE, you can't replace one of these DLLs on your bootable fixed disk unless you reboot the system from a self-contained floppy disk that does not use them. Dynlink libraries differ in important ways from the two other kinds of libraries found in OS/2: object libraries and import libraries. Dynlink libraries are structured as segmented executable (new EXE) files with a special flag in the file header that prevents your running them directly from the command line; the routines in a DLL are suitable for immediate execution and are bound to an application (dynamic linking) at load time or run time. In contrast, object libraries and import libraries are used at link time rather than at execution time and are stored in a special format understood only by the LINK, LIB, and IMPLIB utilities. The code in an object library requires further processing by the Linker before it can be executed, and it is permanently incorporated into a program or a DLL executable file (static linking). The records in import libraries do not usually contain executable code at all; they are merely stubs that represent individual DLL routines and signal the Linker to build the information necessary for dynamic linking into the EXE file header. Dynamic Linking at Load Time Dynamic linking at load time is easy to arrange. In fact, given that most OS/2 API entry points actually reside in dynlink libraries, you can appreciate that you have already been exposed to dynamic linking in every program in this book! In a MASM program, the routines that are to be dynamically linked are simply declared as external with the far attribute, for example: extrn DosWrite:far In a C program, the routines are declared as external with the far pascal attributes (parameters pushed from left to right, and called routine clears the stack), for example: extern unsigned far pascal DosWrite( ... ); If you include the API header files supplied with OS/2 development tools, these external declarations are made for you already. When you link your program, you either specify the module and entry point names for the dynlinked routines with IMPORTS statements in the module definition (DEF) file, or you link the program with an import library that matches the dynlink library or libraries you are going to use at run time. (For example, OS2.LIB contains import records for the Dos, Kbd, Mou, and Vio API entry points.) The result of either method is the same: The Linker builds the names of the dynlink libraries and routines into tables in the EXE file header, along with pointers to each reference to the dynlink libraries in the executable code. Imported dynlink library routines can also be specified in a module definition file by an ordinal rather than by name. An ordinal is merely the index to the position of the routine in the library's entry point table. Using ordinals speeds up dynamic linking and reduces the size of your application's EXE file (because the imported name table in the header will be smaller). But linking with ordinals has its disadvantages. You must be sure that you do not change the ordinals from one version of the library to another, or you must synchronize the library and all the applications that use it. Either way, you forfeit flexibility by using ordinals. When your program is loaded for execution, the system loader inspects the file header's module reference table to determine which dynlink libraries the process uses. If these libraries are not already loaded, the loader invokes itself recursively to bring them into memory (along with any libraries they reference). The loader always assumes that a dynlink library has the extension DLL and that it can be found in one of the directories named in the LIBPATH directive in CONFIG.SYS. If any dynlink library cannot be located, or if memory is exhausted during the loading of libraries, the client program will not be loaded. Some dynlink libraries have an initialization routine which must be called when they are loaded for the first time (global initialization) or each time they are bound to a new process (instance initialization). The type of initialization required, if any, is specified in the library's file header, as is the entry point of the initialization routine. The initialization routine might return an error code if it can't locate resources that the dynlink library will need later (such as a font file, a device, or additional memory), in which case loading of the client program is aborted. When all necessary libraries are loaded and any necessary initialization of those libraries is complete, the loader inspects the segment tables for each module and creates an LDT selector for each program and dynlink library segment. All segments marked PRELOAD are brought into memory, and any necessary fixups are performed. (Segments marked LOADONCALL are not brought into memory and fixed up until they are referenced during program execution.) Dynamic Linking at Run Time Instead of letting the Linker and system loader do all the work of dynamic linking, you can elect to have your program create dynamic links to the libraries it needs (function by function) while it is executing. The API functions for runtime dynamic linking are summarized in Figure 19-1. Function Description DosFreeModule Releases linkage between process and dynlink library DosGetModHandle Returns handle for dynlink library if it is already loaded DosGetModName Retrieves fully qualified pathname of dynlink library DosGetProcAddr Obtains address of entry point within dynlink library DosLoadModule Loads dynlink library if not already loaded and returns handle for use with DosGetProcAddr Figure 19-1. Summary of OS/2 API calls used by applications to link to DLLs at run time. To establish dynamic links at run time, the program must first make the dynlink library's segments and entry points known to the system loader by calling DosLoadModule. This function requires the addresses of an ASCIIZ module name, a buffer, and a variable to receive a handle. The system loader looks in the directories named in the LIBPATH directive for a file with the specified module name and the extension DLL. If the library is found, its PRELOAD segments are brought into memory, any additional libraries that it references are also loaded, and a module handle is returned. If the library (or a library that it uses) is not found, DosLoadModule returns an error code, and the name of the missing library is returned in the calling process's buffer. Assuming that DosLoadModule is successful and a handle is obtained, the program must then obtain the entry points for each library function that it will use. To do so, it calls DosGetProcAddr with a module handle and the address of an ASCIIZ entry point name or an ASCIIZ or binary ordinal. DosGetProcAddr returns a selector and offset for the entry point in a variable. The program can then invoke the library routine by pushing the appropriate parameters onto the stack and performing an indirect far call through the variable. After the program finishes using the dynlink library, it can release the library by calling DosFreeModule with the module handle. If no other process is using the library, it will be discarded and the memory it occupied will be reclaimed. After you call DosFreeModule, any entry points which were previously obtained for the library with DosGetProcAddr become invalid, and any attempt to use the library causes a GP fault. Figure 19-2 contains a skeleton example of runtime dynamic linking in a MASM program. objbuf db 64 dup (0) ; receives failing module ; or entry point name objbuf_len equ $-objbuf mname db 'VIOCALLS',0 ; module name mhandle dw 0 ; receives module handle ename db 'VIOGETANSI',0 ; entry point name eptr dd 0 ; receives far pointer ; to entry point . . . ; attach to DLL... push ds ; receives failing dynlink push offset DGROUP:objbuf push objbuf_len ; length of buffer push ds ; address of module name push offset DGROUP:mname push ds ; receives module handle push offset DGROUP:mhandle call DosLoadModule ; transfer to OS/2 or ax,ax ; module found? jnz error ; jump if no such module ; get entry point... push mhandle ; module handle push ds ; address of entry name push offset DGROUP:ename push ds ; receives entry pointer push offset DGROUP:eptr call DosGetProcAddr ; transfer to OS/2 or ax,ax ; entry point found? jnz error ; jump if no entry point . . ; other processing here... . call eptr ; indirect call to ; dynlink library routine . . ; other processing here... . ; module no longer needed, ; release it... push mhandle ; module handle call DosFreeModule ; transfer to OS/2 or ax,ax ; should never fail jnz error ; but check anyway . . . Figure 19-2. Sample code sequence for runtime dynamic linking. The two other API functions for dynamic linking at run time are rarely used. DosGetModName accepts a module handle (for a library or a process) and retrieves the fully qualified pathname for the module. DosGetModHandle accepts an ASCIIZ module name and returns a handle for the module if it is already loaded, or an error if it is not loaded; DosGetModHandle is strictly an existence test and does not increment the module's use count. Designing New Dynlink Libraries Designing and writing a new dynlink library is a straightforward process in either MASM or C. During the planning stage, you must address the following three design issues: The nature of the DLL interface to applications The type of DLL initialization (if any) The number and behavior of DLL data segments You need to gauge carefully the range and complexity of the functions that the DLL will export to applications. If the functions are too few and too specific, the application programmer will be forced to write his or her own routines for special cases. On the other hand, if the routines are too numerous, the programmer will have difficulty remembering what is available. And if the routines are too primitive, the application programmer will not gain enough leverage by using the functions to make the effort worthwhile. The Vio subsystem that is supplied with OS/2 is an excellent example to imitate. When you write a MASM dynlink library for use with MASM applications, the parameter passing between the application and the DLL can be register-based, stack-based, or a hybrid of the two. When you write a DLL in C, or one that is to be used with C applications, you have less freedom; the structure of the C language more or less forces you to use the same stack-based API as the "standard" OS/2 DLLs, for which the only open question is what each "function" will return as its "value." In either case, however, I strongly recommend that you follow the kernel API conventionspass all parameters to the DLL on the stack, and return a status code in AX and any other information to the caller's variables. Symmetry with the rest of the system will reward you with fewer exceptions to remember and hence fewer bugs to fix. An initialization routine in your DLL is optional. If you provide one, specify its entry point with an END statement in a MASM source file. A dynlink library written in C that requires initialization must, therefore, have at least one small module written in MASM, although the MASM routine which receives control at initialization time can call a C function to do the actual work. If the DLL has an initialization routine, you must determine whether it will be called only when the library is first loaded (global initialization) or each time the library is attached by a client process (instance initialization). Global initialization is especially appropriate when a DLL controls a device; if the device is not available, the initialization routine returns an error. Instance initialization is useful when the DLL allocates private data structures or system resources on a per-client basis. In such cases, the initialization routine should also register a clean-up routine for each client with DosExitList so that the indicated routine will be notified when the client terminates and can clean up any resources or data that have been left hanging. The question of the DLL's data segments goes hand in hand with global or instance initialization. Some DLLs need no data segments of their own those that have no initialized data and no need to store any information across calls from the application, and that can use the stack for all their transient variables. Other DLLs, such as those controlling a single system resource such as a device, might need only a single data segment to store the state of the device and synchronize access to it by multiple processes. The more common case, however, is the DLL which needs multiple unshared segments to store information across function calls on a per-client basis. Each time a new process attaches to the library, OS/2 loads a fresh copy of the DLL's data segments to map into the process's memory space. The system uses identical selectors in each process's LDT so that the same relocations in the DLL's shared code segments will serve for all. The selector magic inherent in OS/2's support for dynlink instance data is one of the most subtle but exquisite features of OS/2's design. During instance initialization, a DLL can also dynamically allocate memory that it can use on behalf of the client process. Although the nature of the DLL's application interface is defined by its source code, the initialization type (global or instance) and behavior of each segment (preload or load-on-call, shared or unshared) is controlled by keywords in the module definition file. Figure 19-3 on the following page provides a summary of the definition file syntax for dynlink libraries (and for device drivers). Compare this summary with the similar figure in Chapter 3 on p. 33; some of the directives and defaults are different from those for applications. Appendix E provides a complete explanation of the syntax for module definition files. Ŀ Ignored if no initialization routine is present. Ŀ LIBRARY [ modulename ][ INITGLOBAL | INITINSTANCE ] DESCRIPTION `string' CODE [ PRELOAD | LOADONCALL ][ EXECUTEONLY | EXECUTEREAD ] [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ] DATA [ PRELOAD | LOADONCALL ][ READONLY | READWRITE ] [ NONE | SINGLE | MULTIPLE ][ SHARED | NONSHARED ] [ IOPL | NOIOPL ] SEGMENTS name [ CLASS [`classname']][ PRELOAD | LOADONCALL ] [ READONLY | READWRITE ][ EXECUTEONLY | EXECUTEREAD ] [ IOPL | NOIOPL ][ CONFORMING | NONCONFORMING ] [ SHARED | NONSHARED ] . Ŀ . Ŀ . Ignored for code segments and read-only data segments; these segments are always shared. EXPORTS exportname [ = internalname ][ @ordinal ][ RESIDENTNAME ][ stackparams ] . Ŀ . Defines the number of stack cells passed to a procedure within an . IOPL segment; when the routine is called, the hardware switches stacks and copies the specified number of words to the new stack. IMPORTS [ internalname = ] modulename.entryname | modulename.ordinal . . . STUB `filename.EXE' OLD `filename.DLL' EXETYPE OS2 Figure 19-3. Module definition file elements used for dynlink libraries and installable device drivers. Keywords must always be entered in uppercase letters. Defaults are in boldface and are not necessarily the same as those supplied for application programs. Writing DLLs in MASM Figure 19-4 contains a code skeleton for a DLL written in MASM. Each procedure in the DLL should set up a stack frame so that it can access its parameters, save any registers that it is going to change, and reset the DS register to make its data segment addressable (if it has one). Procedures that serve as entry points must be declared public and far because they are entered by an intersegment call; procedures that are used only as subroutines within the DLL itself can be declared either near or far. To exit, a procedure should restore the caller's registers (except for AX) and then use RET with an offset to clear the parameters (if any) from the caller's stack. DGROUP group _DATA _DATA segment word public 'DATA' . . . _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP public MYFUNC MYFUNC proc far ; dynlink routine push bp ; set up stack frame mov bp,sp push ax ; save affected registers push bx push cx push dx push si push di push ds mov ax,DGROUP ; make DLL's data mov ds,ax ; segment addressable . . . pop ds ; restore registers pop di pop si pop dx pop cx pop bx pop ax pop bp ret pars*2 ; clear stack, return ; to caller MYFUNC endp INIT proc far ; initialization routine . . . ret INIT endp _TEXT ends end INIT ; initialization entry point Figure 19-4. Skeleton for a DLL written in MASM. If an initialization routine is present, it is identified by the END directive and is also coded as a far procedure. Before calling the DLL initialization entry point, OS/2 sets up the registers as shown in Figure 19-5. All other register values are undefined. If the module saves a copy of its own handle, it can pass the handle to client applications later, and they can use the handle to retrieve the library's fully qualified pathname with DosGetModName. Register Contents CS:IP Entry point defined in END directive SS:SP Current user stack DS Selector for DLL's DGROUP, if any; otherwise, selector for DGROUP of process attaching to DLL AX Module handle of the DLL Figure 19-5. Register contents at entry to a DLL initialization routine. The initialization routine exits with a far return, passing a status code in AX. Initialization routines depart from the conventions observed elsewhere in OS/2: They return zero to indicate an error and a nonzero value to indicate success. If the status returned is zero, the program that referenced the library is not loaded, and the name of the failing library is passed to that program's parent. The module definition file for our skeleton DLL is shown in Figure 19-6. Real DLLs with many segments might have elaborate DEF files, but this one is quite simple. The key elements are the LIBRARY directive, which signals the Linker that entry point and stack declarations may be absent, and the EXPORTS statement, which designates the entry points for applications. Instance or global initialization and DGROUP for the DLL are controlled by optional parameters to the LIBRARY and DATA directives. Assign PRELOAD or LOADONCALL to each segment based on the likelihood that it will be used. In a simple DLL with only two segments (such as this skeleton example), both might as well be marked PRELOAD so that the time required to fetch them from disk will be hidden inside the overall load time of the application. LIBRARY MYDLL INITGLOBAL CODE PRELOAD DATA PRELOAD MULTIPLE EXPORTS MYFUNC Figure 19-6. Module definition file for the skeleton MASM dynlink library. A Complete Sample DLL Figure 19-7 (on the following pages) and Figure 19-8 (on p. 476) contain the MASM source code and module definition file for a complete sample dynlink library called ASMHELP.DLL. The library exports two routines for use by MASM applications: ARGC, which counts the number of command line arguments, and ARGV, which returns the address and length of a specific command line argument. title ASMHELP -- Sample MASM DLL page 55,132 .286 ; ; ASMHELP.ASM ; ; Source code for the MASM dynlink library ASMHELP.DLL. ; ; Assemble with C> masm asmhelp.asm; ; Link with C> link asmhelp,asmhelp.dll,,os2,asmhelp ; ; Exports two routines for use by MASM programs: ; ; ARGC Returns count of command line arguments. ; Treats blanks and tabs as whitespace. ; ; Calling sequence: ; ; push seg argcnt ; receives argument count ; push offset argcnt ; call ARGC ; ; ARGV Returns address and length of specified ; command line argument. If called for ; argument 0, returns address and length ; of fully qualified pathname of program. ; ; Calling sequence: ; ; push argno ; argument number ; push seg argptr ; receives argument address ; push offset argptr ; push seg arglen ; receives argument length ; push offset arglen ; call ARGV ; ; Copyright (C) 1988 Ray Duncan ; tab equ 09h ; ASCII tab blank equ 20h ; ASCII space character extrn DosGetEnv:far DGROUP group _DATA _DATA segment word public 'DATA' envseg dw ? ; environment selector cmdoffs dw ? ; command tail offset _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP ; parameter for argc argcnt equ [bp+6] ; receives argument count public argc argc proc far ; count command line arguments push bp ; make arguments addressable mov bp,sp push ds ; save registers push es push bx push cx mov ax,seg DGROUP ; point to instance data mov ds,ax mov es,envseg ; set ES:BX = command line mov bx,cmdoffs mov ax,1 ; initialize argument count argc0: inc bx ; ignore useless first field cmp byte ptr es:[bx],0 jne argc0 argc1: mov cx,-1 ; set flag = outside argument argc2: inc bx ; point to next character cmp byte ptr es:[bx],0 je argc3 ; exit if null byte cmp byte ptr es:[bx],blank je argc1 ; outside argument if ASCII blank cmp byte ptr es:[bx],tab je argc1 ; outside argument if ASCII tab ; otherwise not blank or tab, jcxz argc2 ; jump if already inside argument inc ax ; else found argument, count it not cx ; set flag = inside argument jmp argc2 ; and look at next character argc3: ; store result into ; caller's variable... les bx,argcnt ; get address of variable mov es:[bx],ax ; store argument count pop cx ; restore registers pop bx pop es pop ds pop bp xor ax,ax ; signal success ret 4 ; return to caller ; and discard parameters argc endp ; parameters for argv... argno equ [bp+14] ; argument number argptr equ [bp+10] ; receives argument pointer arglen equ [bp+6] ; receives argument length public argv argv proc far ; get address and length of ; command tail argument push bp ; make arguments addressable mov bp,sp push ds ; save registers push es push bx push cx push di mov ax,seg DGROUP ; point to instance data mov ds,ax mov es,envseg ; set ES:BX = command line mov bx,cmdoffs mov ax,argno ; get argument number or ax,ax ; requesting argument 0? jz argv8 ; yes, get program name argv1: inc bx ; scan off first field cmp byte ptr es:[bx],0 jne argv1 xor ah,ah ; initialize argument counter argv2: mov cx,-1 ; set flag = outside argument argv3: inc bx ; point to next character cmp byte ptr es:[bx],0 je argv7 ; exit if null byte cmp byte ptr es:[bx],blank je argv2 ; outside argument if ASCII blank cmp byte ptr es:[bx],tab je argv2 ; outside argument if ASCII tab ; if not blank or tab... jcxz argv3 ; jump if inside argument inc ah ; else count arguments found cmp ah,al ; is this the one? je argv4 ; yes, go find its length not cx ; no, set flag = inside argument jmp argv3 ; and look at next character argv4: ; found desired argument, now ; determine its length... mov ax,bx ; save parameter starting address argv5: inc bx ; point to next character cmp byte ptr es:[bx],0 je argv6 ; found end if null byte cmp byte ptr es:[bx],blank je argv6 ; found end if ASCII blank cmp byte ptr es:[bx],tab jne argv5 ; found end if ASCII tab argv6: xchg bx,ax ; set ES:BX = argument address sub ax,bx ; and AX = argument length jmp argv10 ; return to caller argv7: mov ax,1 ; set AX != 0 indicating ; error, argument not found jmp argv11 ; return to caller argv8: ; special handling for argv = 0 xor di,di ; find the program name by xor al,al ; first skipping over all the mov cx,-1 ; environment variables... cld argv9: repne scasb ; scan for double null (can't use SCASW scasb ; because it might be odd address) jne argv9 ; loop if it was a single null mov bx,di ; save program name address mov cx,-1 ; now find its length... repne scasb ; scan for another null byte not cx ; convert CX to length dec cx mov ax,cx ; return length in AX argv10: ; at this point AX = length, ; ES:BX points to argument lds di,argptr ; address of first variable mov ds:[di],bx ; store argument pointer mov ds:[di+2],es lds di,arglen ; address of second variable mov ds:[di],ax ; store argument length xor ax,ax ; AX = 0 to signal success argv11: ; common exit point pop di ; restore registers pop cx pop bx pop es pop ds pop bp ret 10 ; return to caller ; and discard parameters argv endp init proc far ; DLL instance initialization ; get environment selector ; and offset of command tail ; for this process... push seg DGROUP ; receives environment selector push offset DGROUP:envseg push seg DGROUP ; receives command tail offset push offset DGROUP:cmdoffs call DosGetEnv ; transfer to OS/2 or ax,ax ; call successful? jnz init1 ; no, initialization error mov ax,1 ; initialization OK, ret ; return AX = 1 for success init1: xor ax,ax ; initialization failed, ret ; return AX = 0 for error init endp _TEXT ends end init ; initialization entry point Figure 19-7. ASMHELP.ASM, the MASM source code for the dynlink library ASMHELP.DLL. LIBRARY ASMHELP INITINSTANCE PROTMODE CODE LOADONCALL DATA LOADONCALL NONSHARED EXPORTS ARGC ARGV Figure 19-8. ASMHELP.DEF, the module definition file for the dynlink library ASMHELP.DLL. The routines in ASMHELP.DLL are exactly analogous to the argc and argv available to C programs. Both are written according to the OS/2 API conventions. Parameters and the addresses of variables to receive results are passed on the stack. A status code is returned in AX (zero for no error), and any other information is placed in variables specified by the caller. The routine ARGC always returns a value of 1 or greaterthe name of the program itself is counted as a command line argument. The parameter for ARGV is zero-based; when it is zero, the routine extracts from the environment the fully qualified pathname from which the current process was loaded; for other values, it fetches the corresponding token from the command tail. To create ASMHELP.DLL, type the following sequence of commands: [C:\] MASM ASMHELP.ASM; [C:\] LINK ASMHELP,ASMHELP.DLL,,OS2,ASMHELP Then copy the file to one of the directories named in the LIBPATH directive in CONFIG.SYS so that the system loader can find it. Figure 19-9 and Figure 19-10 (on p. 480) contain the source code and module definition file for SHOWARGS.EXE, a simple application that calls both routines in ASMHELP.DLL. To create SHOWARGS.EXE, type the following sequence of commands: [C:\] MASM SHOWARGS.ASM; [C:\] LINK SHOWARGS,,,OS2,SHOWARGS The following is a sample SHOWARGS session: [C:\] showargs foo bar The command line contains 03 arguments Argument 00 is: C:\SOURCE\OS2\DLL\SHOWARGS.EXE Argument 01 is: foo Argument 02 is: bar [C:\] title SHOWARGS -- ASMHELP.DLL Demo page 55,132 .286 ; ; SHOWARGS.ASM ; ; Demonstrates parsing of command line by calls to ASMHELP.DLL. ; ; Assemble with: C> masm showargs.asm; ; Link with: C> link showargs,,,os2,showargs ; ; Usage is: C> showargs [argument(s)] ; ; Copyright (C) 1988 Ray Duncan ; stdin equ 0 ; standard input handle stdout equ 1 ; standard output handle stderr equ 2 ; standard error handle cr equ 0dh ; ASCII carriage return lf equ 0ah ; ASCII linefeed blank equ 020h ; ASCII blank tab equ 09h ; ASCII tab ; references to ASMHELP.DLL extrn ARGC:far ; returns argument count extrn ARGV:far ; returns pointer to argument extrn DosWrite:far ; references to OS/2 extrn DosExit:far extrn DosGetEnv:far DGROUP group _DATA _DATA segment word public 'DATA' argno dw 0 ; receives argument count argptr dw 0,0 ; receives argument pointer arglen dw 0 ; receives argument length curarg dw 0 ; current command line argument wlen dw 0 ; bytes actually written msg1 db cr,lf db 'The command line contains ' msg1a db 'xx arguments' msg1_len equ $-msg1 msg2 db cr,lf db 'Argument ' msg2a db 'xx is: ' msg2_len equ $-msg2 _DATA ends _TEXT segment word public 'CODE' assume cs:_TEXT,ds:DGROUP main proc far ; entry point from OS/2 ; get number of command ; line arguments... push ds ; receives argument count push offset DGROUP:argno call argc ; call ASMHELP.DLL mov ax,argno ; convert count to ASCII mov bx,offset msg1a ; for output call b2dec ; now display number ; of arguments... push stdout ; standard output handle push ds ; address of message push offset DGROUP:msg1 push msg1_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 main1: ; display next argument... mov ax,curarg ; are we all done? cmp ax,argno je main2 ; yes, exit mov bx,offset msg2a ; no, convert argument number call b2dec ; display argument number... push stdout ; standard output handle push ds ; address of message push offset DGROUP:msg2 push msg2_len ; length of message push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 ; get argument pointer... push curarg ; argument number push ds ; receives pointer push offset DGROUP:argptr push ds ; receives length push offset DGROUP:arglen call argv ; call ASMHELP.DLL ; display the argument... push stdout ; standard output handle push argptr+2 ; pointer to argument push argptr push arglen ; length of argument push ds ; receives bytes written push offset DGROUP:wlen call DosWrite ; transfer to OS/2 inc word ptr curarg ; go to next argument jmp main1 main2: ; common exit point push 1 ; terminate all threads push 0 ; return code = zero call DosExit ; transfer to OS/2 main endp b2dec proc near ; convert binary value 0-99 ; to two decimal ASCII ; call with ; AL = binary data ; BX = address for 2 characters aam ; divide AL by 10, leaving ; AH = quotient, AL = remainder add ax,'00' ; convert to ASCII mov [bx],ah ; store tens digit mov [bx+1],al ; store ones digit ret ; return to caller b2dec endp _TEXT ends end main ; defines entry point Figure 19-9. SHOWARGS.ASM, source code for sample program SHOWARGS.EXE, which uses the dynlink library ASMHELP.DLL. NAME SHOWARGS PROTMODE STACKSIZE 4096 IMPORTS ASMHELP.ARGC ASMHELP.ARGV Figure 19-10. SHOWARGS.DEF, module definition file for SHOWARGS.EXE. Using Import Libraries If your dynlink library contains many entry points, you can make life much easier for the programmers who use it by creating a matching import library. When an application that calls the dynlink library is being built, the Linker can use the import library to satisfy internal references to the dynlink library routines, thereby eliminating the need for IMPORTS statements in the application's module definition file. Import libraries are built by the IMPLIB.EXE utility, which is supplied in the OS/2 Programmer's Toolkit. The parameters required by IMPLIB are the name of the import library to be built and the name of one or more module definition files containing EXPORTS statements for dynlink library entry points. The command line for IMPLIB takes the following form: IMPLIB libfile.LIB deffile.DEF [deffile.DEF ...] where the extensions LIB and DEF must be supplied as shown. For example, to create an import library for ASMHELP.DLL, the command line would be the following: [C:\] IMPLIB ASMHELP.LIB ASMHELP.DEF Once ASMHELP.LIB is available, you can remove the IMPORT statements from SHOWARGS.DEF and link the utility using the import library instead: [C:\] LINK SHOWARGS,,,OS2+ASMHELP,SHOWARGS Writing DLLs in C If you prefer to write your DLL in C, you need to use a few special tricks. First, each C function that will serve as an entry point from an application must be declared far pascal. Other functions, which are used only within the library, can use whichever style of parameter passing you prefer, although the pascal convention generates smaller, faster code. Pay close attention to every pointer because a dynlink routine is in some ways an extreme example of mixed-model C programming. Finally, include the following statement to prevent the C startup code from being dragged into the library by the Linker: int _acrtused = 0; When you compile your dynlink library, use the following switches: Switch Meaning /c Compile only (to allow you to link separately) /Asnu Small model; assume DS not equal to SS; save DS and reset it to point to the library's data segment at entry to each function /Gs Disable stack checking In some cases, you may also need to use the /ND switch to name the data segment that is made addressable when a DLL function is entered. The initialization routine, if any, must be written in MASM and assembled separately using the /Mx switch so that case is preserved in public and external names. The C-language body of the DLL must then be linked together with the MASM startup routine, OS2.LIB, and an appropriate module definition file. Figure 19-11 on the following page contains the source code (CDLL.C) for a simple dynlink library, one that merely displays a message when its initialization routine and exported function are called. Figure 19-12 on p. 484 contains the MASM source code (CINIT.ASM) for the initialization entry point, and Figure 19-13 on p. 484 is the module definition file (CDLL.DEF). /* CDLL.C C source code for the dynlink library CDLL.LIB. Requires the module CINIT.ASM. Compile with: C> cl /c /Asnu /Gs cdll.c Assemble CINIT.ASM with: C> masm /Mx cinit.asm; Link with: C> link /NOI /NOD cdll+cinit,cdll.dll,,os2,cdll Create CDLL.LIB with: C> implib cdll.lib cdll.def Copyright (C) 1988 Ray Duncan /* int _acrtused = 0; /* don't link startup */ #define STDOUT 1 /* standard output handle */ #define API unsigned extern far pascal API DosWrite(unsigned, void far *, unsigned, unsigned far *); static char funcmsg[] = "\nCDLL.MYFUNC is executing\n"; static char initmsg[] = "\nCDLL.C_INIT is executing\n"; /* MYFUNC is exported for use by application programs; it displays a message and returns the sum of 2 numbers. */ int far pascal MYFUNC(int a, int b) { unsigned wlen; /* receives length written */ /* display message that MYFUNC is executing */ DosWrite(STDOUT,funcmsg,sizeof(funcmsg)-1,&wlen); return(a+b); /* return function result */ } /* C_INIT is called from the entry point in CINIT.ASM when a client process dynamically links to the library. */ int far pascal C_INIT(void) { unsigned wlen; /* receives length written */ /* display message that C_INIT is executing */ DosWrite(STDOUT,initmsg,sizeof(initmsg)-1,&wlen); return(1); /* return success code */ } Figure 19-11. CDLL.C, source code for a simple dynlink library written in C. The function MYFUNC is exported for use by application programs; it displays a message and returns the sum of two numbers. The initialization function C_INIT is called when the DLL is first loaded from the entry point in CINIT.ASM (see Figure 19-12); in this example, it merely displays a message and returns a success code. extrn C_INIT:far _TEXT segment word public 'CODE' assume cs:_TEXT INIT proc far ; initialization routine call C_INIT ; call C routine to ; do the actual work ret ; back to caller with ; AX = status code ; ("value" from C_INIT) INIT endp _TEXT ends end INIT Figure 19-12. CINIT.ASM, the MASM module containing the initialization entry point for CDLL.DLL (see Figure 19-11). The MASM routine INIT simply calls the C function C_INIT to do the actual work of initialization. LIBRARY CDLL INITINSTANCE PROTMODE CODE LOADONCALL DATA LOADONCALL NONSHARED EXPORTS MYFUNC Figure 19-13. CDLL.DEF, the module definition file for CDLL.DLL (see also Figures 19-11 and 19-12). To build the dynlink library CDLL.DLL, you would enter the following commands: [C:\] CL /c /Asnu /Gs CDLL.C [C:\] MASM /Mx CINIT.ASM; [C:\] LINK /NOI /NOD CDLL+CINIT,CDLL.DLL,,OS2,CDLL You can then create an import library for CDLL.DLL with the following command: [C:\] IMPLIB CDLL.LIB CDLL.DEF The entire process of building a dynlink library and an import library from MASM or C source code (or both) is diagrammed in Figure 19-14. If you use C runtime library functions in your DLL, be sure to use the special reentrant libraries so that your DLL can be used safely by more than one thread or process at a time. Ŀ Ŀ MASM C source source code code file file (ASM) (C) MASM Compiler Ŀ Relocatable object module Ŀ file (OBJ) LIB Ŀ Object module library file  (LIB) Ŀ Ŀ Import  LINK Dynlink library  library OS2.LIB  (DLL) Ŀ Other import  libraries Ŀ C runtime  libraries Ŀ Module Ŀ definition IMPLIB Import file (DEF)  library (LIB) Figure 19-14. Steps in the creation of an OS/2 dynlink library. SECTION 6 OS/2 REFERENCE Part 1 Kernel API The entries in Part 1 of this reference section describe each of the kernel Application Program Interface (API) functions available in OS/2 versions 1.0 and 1.1. The API functions specific to the Presentation Manager are, of course, beyond the scope of this book. The DosDevIOCtl subfunctions are documented in Part 2 of this section, and the DevHlp (Device Driver Helper) functions, which cannot be called by application programs, are documented in Part 3. Application programs invoke API functions by a far call to a named entry point. Parameters are passed on the stack; these may be values or pointers to variables (commonly structures). All kernel API functions return a status in register AX: zero if the function was successful, or a nonzero error code; the remaining registers are preserved. Other results are always returned in variables. A list of error codes is provided in Appendix A. In this section, the calling sequence for each API function is described in a unified format that contains both the function's arguments and its results (aside from the status in AX). Parameters passed by value (such as WORD, DWORD) are always inputs to the function. Parameters passed by reference (such as PTR WORD) can be static data (such as a filename) or variables. In the latter case, the same storage locations can be used both to contain arguments and to receive results. The following conventions are used to describe parameters passed by reference, in cases where the description would otherwise be ambiguous: "Contains" in the description indicates an input to the function. The program is responsible for placing the proper value in the variable before making the API function call. "Receives" indicates that the variable will receive a result; the program inspects the value after it returns from the function. In nonambiguous cases (for example, a pointer to an ASCIIZ filename), the word "contains" or "receives" is not needed and is not used. The term "pathname" refers to a directory or file specification that can include a drive, path (relative or complete), filename, and extension. A "fully qualified pathname" always includes a drive and a complete path from the root directory. The following icons are used in this section: [1.1] API function added in OS/2 version 1.1; not available in version 1.0. [IOPL] Function that can be called from a ring 2 (IOPL) routine in OS/2 version 1.1, a so-called conforming function. [XPM] Function that does nothing or is not allowed if the application is running in a Presentation Manager window. [AVIO] Advanced Vio function that is present to support the Presentation Manager and should not ordinarily be used by Kernel applications. [FAPI] Family API function that can be used in a bound (dual-mode) application program. Some FAPI functions are restricted when used in real mode; in such cases, the notes for the entry provide details. Using the Kernel API Reference Each OS/2 kernel API function is described in the following format: Ŀ The function name and any applicableĴ DosGetFramis [FAPI] icons Ŀ A synopsis of the Returns the framis value for the specified screen function's purposeĴ group. and usage Ŀ WORD Wait/no-wait option The application 0 = Wait until framis value is available program interface 1 = Return immediately with error if to the function, framis is busy; do not wait until with a descriptionĴ framis is available of any results re- PTR WORD Receives the framis value turned by the WORD Logical framis handle (0 = default) function Ŀ Note: Information about special uses of Ĵ In real mode, the logical framis handle is the function always zero. Ŀ Other API Related function: DosSetFramis functions that are closely related or that perform sim- ilar operations In an assembly language program, you might code a call to this function as follows: extrn DosGetFramis:far . . . framis dw 0 ; receives framis value . . . push 0 ; wait if necessary push ds ; receives framis value push offset DGROUP:framis push 0 ; logical framis handle call DosGetFramis ; transfer to OS/2 or ax,ax ; did function succeed? jnz error ; jump if function failed In a C program, you might code a call to this function as follows: #define WAIT 0 #define NO_WAIT 1 extern unsigned far pascal DosGetFramis(unsigned, unsigned far *, unsigned); . . . unsigned framis, status; . . . status = DosGetFramis(WAIT, &framis, 0); Kernel API Functions Listed Alphabetically Function Description DosAllocHuge Allocates huge memory block (> 64 KB) DosAllocSeg Allocates memory block (<= 64 KB) DosAllocShrSeg Allocates named shareable memory block DosBeep Generates tone DosBufReset Flushes file buffers, updates directory DosCallback Invokes ring 3 procedure on behalf of ring 2 (IOPL) code DosCallNmPipe Opens, writes, reads, and closes named pipe DosCaseMap Translates ASCII string in place DosChDir Selects current directory DosChgFilePtr Sets file pointer position DosCLIAccess Notifies intent to use CLI and STI DosClose Closes file, pipe, or device DosCloseQueue Closes queue (destroys if owner) DosCloseSem Closes system semaphore DosConnectNmPipe Waits for client to open pipe DosCreateCSAlias Obtains executable alias for data segment DosCreateQueue Creates named queue DosCreateSem Creates named system semaphore DosCreateThread Creates new execution thread in current process DosCwait Waits for termination of child process DosDelete Deletes file DosDevConfig Returns system hardware configuration DosDevIOCtl Device-specific commands and information DosDisConnectNmPipe Unilaterally closes named pipe DosDupHandle Duplicates or redirects handle DosEnterCritSec Suspends all other threads in process DosErrClass Returns information about error code DosError Disables or enables system critical error handler DosExecPgm Creates child process DosExit Terminates thread or process DosExitCritSec Reactivates other threads in same process DosExitList Registers routines to be executed at process termination DosFileLocks Locks or unlocks file region DosFindClose Releases directory search handle DosFindFirst Searches for first matching file DosFindNext Searches for additional matching files DosFlagProcess Sends event flag signal to another process DosFreeModule Releases handle for dynlink library DosFreeSeg Releases selector DosFSRamSemClear Releases fast-safe RAM semaphore DosFSRamSemRequest Obtains ownership of fast-safe RAM semaphore DosGetCollate Returns collating sequence table DosGetCp Returns current code page identifier DosGetCtryInfo Returns internationalization information DosGetDateTime Returns current time, date, and day of week DosGetDBCSEv Returns table of double-byte character-set codes DosGetEnv Returns selector for process's environment DosGetHugeShift Returns incrementing value for huge memory block selectors DosGetInfoSeg Returns selectors for global and local read-only information segments DosGetMachineMode Returns flag indicating real or protected mode DosGetMessage Retrieves message from disk file DosGetModHandle Obtains handle for dynlink library DosGetModName Obtains filename of dynlink library DosGetPID Returns PID of current process and its parent DosGetPPID Returns PID of process's parent DosGetProcAddr Obtains entry point for dynlink library routine DosGetPrty Returns priority of thread DosGetResource Returns selector for read-only resource DosGetSeg Obtains selector for "gettable" memory segment DosGetShrSeg Obtains selector for existing named shareable segment DosGetVersion Returns OS/2 version number DosGiveSeg Obtains addressability for segment on behalf of another process DosHoldSignal Suspends signal processing for current process DosInsMessage Inserts variable text into body of message DosKillProcess Terminates another process DosLoadModule Loads dynlink library if it is not already loaded DosLockSeg Marks segment as not discardable DosMakeNmPipe Creates named pipe and returns handle DosMakePipe Creates anonymous pipe DosMemAvail Returns size of largest free block of physical memory DosMkDir Creates directory DosMonClose Terminates monitoring for character device DosMonOpen Returns handle for device monitoring DosMonRead Obtains monitor data packet DosMonReg Registers buffers for device monitor DosMonWrite Returns data packet to monitor chain DosMove Renames and/or moves file DosMuxSemWait Waits for one or more semaphores to become clear DosNewSize Changes size of file DosOpen Opens, replaces, or creates file, or opens device DosOpenQueue Opens existing named queue DosOpenSem Opens existing named system semaphore DosPeekNmPipe Reads named pipe without removing data from pipe DosPeekQueue Returns pointer to queue record without removing it from queue DosPFSActivate Selects printer code page and font DosPFSCloseUser Notifies font switcher that spool file is closed DosPFSInit Initializes printer code page and font switching DosPFSQueryAct Returns printer code page and font ID DosPFSVerifyFont Checks existence of printer code page and font DosPhysicalDisk Returns information about partitionable fixed disks DosPortAccess Notifies intent to use range of I/O ports DosPTrace Allows controlled execution of another process DosPurgeQueue Discards all records in queue DosPutMessage Sends message to file, pipe, or device DosQAppType Returns application type (PM-aware, PM-compatible, etc.) DosQCurDir Returns current directory DosQCurDisk Returns current disk drive DosQFHState Returns file handle sharing and access characteristics DosQFileInfo Returns file size, attributes, and date and time stamps DosQFileMode Returns file attributes DosQFSInfo Returns file system information or volume label DosQHandType Returns handle type (file, pipe, or device) DosQNmPHandState Returns modes associated with named pipe handle DosQNmPipeInfo Returns characteristics of named pipe DosQNmPipeSemState Returns information for pipes associated with semaphore DosQueryQueue Returns number of records in queue DosQSysInfo Returns miscellaneous system information DosQVerify Returns state of read-after-write flag DosR2StackRealloc Increases size of stack used in ring 2 (IOPL) segment DosRead Reads data from file, pipe, or device DosReadAsync Asynchronous form of DosRead DosReadQueue Reads and removes record from queue DosReallocHuge Resizes huge memory block DosReallocSeg Resizes normal memory block DosResumeThread Reactivates thread in same process DosRmDir Deletes directory DosScanEnv Searches environment for string DosSearchPath Searches one or more directories for file DosSelectDisk Selects current disk drive DosSelectSession Switches session into foreground DosSemClear Unconditionally clears semaphore DosSemRequest Obtains ownership of semaphore DosSemSet Unconditionally sets semaphore DosSemSetWait Unconditionally sets semaphore and waits until it is cleared DosSemWait Waits until semaphore is cleared DosSendSignal Sends Ctrl-C or Ctrl-Break signal to another process DosSetCp Selects code page for process and session DosSetDateTime Sets current date and time DosSetFHState Sets file handle characteristics DosSetFileInfo Sets file time and date stamps DosSetFileMode Sets file attributes DosSetFSInfo Adds or changes volume label DosSetMaxFH Sets maximum handles for current process DosSetNmPHandState Sets modes for named pipe handle DosSetNmPipeSem Associates system semaphore with named pipe DosSetProcCp Selects code page for process only DosSetPrty Sets priority of thread DosSetSession Sets session characteristics DosSetSigHandler Registers signal handler DosSetVec Registers handler for hardware exception DosSetVerify Sets system read-after-write flag DosSizeSeg Returns size of previously allocated segment DosSleep Suspends requesting thread for specified interval DosStartSession Creates new session and starts process within that session DosStopSession Terminates session DosSubAlloc Allocates memory from local heap DosSubFree Releases memory in local heap DosSubSet Initializes local heap DosSuspendThread Suspends execution of thread in same process DosTimerAsync Starts asynchronous one-shot timer DosTimerStart Starts asynchronous repeating timer DosTimerStop Stops timer DosTransactNmPipe Writes, then reads, named pipe DosUnlockSeg Marks segment as discardable DosWaitNmPipe Waits for availability of named pipe instance DosWrite Writes to file, pipe, or device DosWriteAsync Asynchronous form of DosWrite DosWriteQueue Writes record into queue KbdCharIn Returns keyboard character and scan code KbdClose Destroys logical keyboard KbdDeRegister Deregisters alternate keyboard subsystem KbdFlushBuffer Discards waiting keyboard characters KbdFreeFocus Releases bond between physical and logical keyboard KbdGetCp Returns keyboard code page identifier KbdGetFocus Binds logical to physical keyboard KbdGetStatus Returns logical keyboard status KbdOpen Creates logical keyboard KbdPeek Returns character-waiting status KbdRegister Registers alternate keyboard subsystem KbdSetCp Selects keyboard code page KbdSetCustXT Installs custom scan code translation table KbdSetStatus Sets logical keyboard characteristics KbdStringIn Reads buffered line from keyboard KbdSynch Serializes access to keyboard driver KbdXlate Translates scan code to ASCII character MouClose Destroys logical mouse handle MouDeRegister Deregisters alternate mouse subsystem MouDrawPtr Displays mouse pointer MouFlushQue Discards waiting mouse events MouGetDevStatus Returns status of mouse driver MouGetEventMask Returns event mask for changes in mouse state MouGetHotKey Returns Task Manager mouse hot key if any MouGetNumButtons Returns number of supported mouse buttons MouGetNumMickeys Returns units of mouse movement per centimeter MouGetNumQueEl Returns number of waiting mouse events MouGetPtrPos Returns mouse pointer position MouGetPtrShape Returns mouse pointer shape and size MouGetScaleFact Returns scaling factors for mouse movement MouInitReal Initializes mouse driver for real mode screen group MouOpen Returns handle for mouse device MouReadEventQue Reads and removes mouse event packet from driver queue MouRegister Registers alternate mouse subsystem MouRemovePtr Defines exclusion area for mouse pointer MouSetDevStatus Sets mouse driver characteristics MouSetEventMask Sets event mask for changes in mouse state MouSetHotKey Sets mouse button combination for Task Manager hot key MouSetPtrPos Sets mouse pointer position MouSetPtrShape Sets mouse pointer shape and size MouSetScaleFact Sets scaling factors for mouse movement MouSynch Serializes access to mouse driver VioAssociate Associates Vio presentation space with device context VioCreateLogFont Creates logical font for use with Vio presentation space VioCreatePS Allocates Vio presentation space VioDeleteSetId Releases logical font identifier VioDeRegister Deregisters alternate video subsystem VioDestroyPS Destroys Vio presentation space VioEndPopUp Releases control of screen and keyboard by background process VioGetAnsi Returns code for ANSI support enabled/disabled VioGetBuf Returns address of logical video buffer VioGetConfig Returns hardware characteristics of video adapter VioGetCp Returns video code page identifier VioGetCurPos Returns cursor position VioGetCurType Returns cursor shape, size, and attribute VioGetDeviceCellSize Returns character cell height and width VioGetFont Returns address of font table VioGetMode Returns characteristics of current display mode VioGetOrg Returns screen origin for Vio presentation space VioGetPhysBuf Returns selector for physical video display buffer VioGetState Returns palette registers, border color, intensity/blink toggle VioModeUndo Cancels VioModeWait call by another thread VioModeWait Blocks thread until video adapter state restore needed VioPopUp Asserts control of screen and keyboard by background process VioPrtSc Copies screen contents to printer VioPrtScToggle Enables or disables character echo to printer VioQueryFonts Returns list of available fonts for typeface VioQuerySetIds Returns list of loaded fonts VioReadCellStr Retrieves string of character-attribute pairs from display buffer VioReadCharStr Retrieves character string from display buffer VioRegister Registers alternate video subsystem VioSavRedrawUndo Cancels VioSavRedrawWait call by another thread VioSavRedrawWait Blocks thread until video buffer and adapter state restore needed VioScrLock Locks physical display for I/O VioScrollDn Scrolls display down VioScrollLf Scrolls display left VioScrollRt Scrolls display right VioScrollUp Scrolls display up VioScrUnLock Releases lock on physical display VioSetAnsi Enables or disables interpretation of ANSI escape sequences VioSetCp Selects video code page VioSetCurPos Sets cursor position VioSetCurType Sets cursor shape, size, and attribute VioSetDeviceCellSize Sets character cell height and width VioSetFont Downloads display font into adapter VioSetMode Selects display mode VioSetOrg Sets screen origin for Vio presentation space VioSetState Sets palette register values, border color, or intensity/blink toggle VioShowBuf Updates physical display from logical display buffer VioShowPS Updates display from Vio presentation space VioWrtCellStr Writes character-attribute pairs to display VioWrtCharStr Writes character string to display VioWrtCharStrAtt Writes character string with specified attribute to display VioWrtNAttr Changes attributes of one or more characters on display VioWrtNCell Initializes one or more display positions with character-attribute pair VioWrtNChar Initializes one or more display positions with character VioWrtTTY Writes character string to display in teletype mode Dos API by Functional Group Function Description File and Device Management DosBufReset Flushes file buffers, updates directory DosChgFilePtr Sets file pointer position DosClose Closes file, pipe, or device DosDelete Deletes file DosDupHandle Duplicates or redirects handle DosFileLock Locks or unlocks file region DosMove Renames and/or moves file DosNewSize Changes size of file DosOpen Opens, replaces, or creates file, or opens device DosQFHState Returns file handle sharing and access characteristics DosQFileInfo Returns file size, attributes, and date and time stamps DosQFileMode Returns file attributes DosQHandType Returns handle type (file, pipe, or device) DosRead Reads data from file, pipe, or device DosReadAsync Asynchronous form of DosRead DosSetFHState Sets file handle characteristics DosSetFileInfo Sets file time and date stamps DosSetFileMode Sets file attributes DosSetMaxFH Sets maximum handles for current process DosWrite Writes to file, pipe, or device DosWriteAsync Asynchronous form of DosWrite Disk and Directory Management DosChDir Selects current directory DosFindClose Releases directory search handle DosFindFirst Searches for first matching file DosFindNext Searches for additional matching files DosMkDir Creates directory DosPhysicalDisk Returns information about partitionable fixed disks DosQCurDir Returns current directory DosQCurDisk Returns current disk drive DosQFSInfo Returns file system information or volume label DosQVerify Returns state of read-after-write flag DosRmDir Deletes directory DosSearchPath Searches one or more directories for file DosSelectDisk Selects current disk drive DosSetFSInfo Adds or changes volume label DosSetVerify Sets system read-after-write flag Memory Management DosAllocHuge Allocates huge memory block (> 64 KB) DosAllocSeg Allocates memory block (<= 64 KB) DosAllocShrSeg Allocates named shareable memory block DosCreateCSAlias Obtains executable alias for data segment DosFreeSeg Releases selector DosGetHugeShift Returns incrementing value for huge memory block selectors DosGetResource Returns selector for read-only resource DosGetSeg Obtains selector for "gettable" memory segment DosGetShrSeg Obtains selector for existing named shareable segment DosGiveSeg Obtains addressability for segment on behalf of another process DosLockSeg Marks segment as not discardable DosMemAvail Returns size of largest free block of physical memory DosR2StackRealloc Increases size of stack used in IOPL segment DosReallocHuge Resizes huge memory block DosReallocSeg Resizes normal memory block DosSizeSeg Returns size of previously allocated segment DosSubAlloc Allocates memory from local heap DosSubFree Releases memory in local heap DosSubSet Initializes local heap DosUnlockSeg Marks segment as discardable Thread Management DosCreateThread Creates new execution thread in current process DosEnterCritSec Suspends all other threads in process DosExit Terminates thread DosExitCritSec Reactivates other threads in same process DosGetPrty Returns priority of thread DosResumeThread Reactivates thread in same process DosSetPrty Sets priority of thread DosSuspendThread Suspends execution of thread in same process Process Management DosCwait Waits for termination of child process DosExecPgm Creates child process DosExit Terminates process DosExitList Registers routines to be executed at process termination DosGetPID Returns PID of current process and its parent DosGetPPID Returns PID of process's parent DosKillProcess Terminates process DosPTrace Allows controlled execution for debugging of another process Session Management DosSelectSession Switches session into foreground DosSetSession Sets session characteristics DosStartSession Creates new session and starts process within that session DosStopSession Terminates session Semaphore Management DosCloseSem Closes system semaphore DosCreateSem Creates named system semaphore DosFSRamSemClear Releases fast-safe RAM semaphore DosFSRamSemRequest Obtains ownership of fast-safe RAM semaphore DosMuxSemWait Waits for one or more semaphores to become clear DosOpenSem Opens existing named system semaphore DosSemClear Unconditionally clears semaphore DosSemRequest Obtains ownership of semaphore DosSemSet Unconditionally sets semaphore DosSemSetWait Unconditionally sets semaphore and waits until it is cleared DosSemWait Waits until semaphore is cleared Pipe Management DosCallNmPipe Opens, writes, reads, and closes named pipe DosConnectNmPipe Waits for client to open pipe DosDisConnectNmPipe Unilaterally closes named pipe DosMakeNmPipe Creates named pipe DosMakePipe Creates anonymous pipe DosPeekNmPipe Reads data from named pipe without removing it from pipe DosQNmPHandState Returns modes associated with named pipe handle DosQNmPipeInfo Returns characteristics of named pipe DosQNmPipeSemState Returns information for pipes associated with semaphore DosSetNmPHandState Sets modes for named pipe handle DosSetNmPipeSem Associates system semaphore with named pipe DosTransactNmPipe Writes, then reads, named pipe DosWaitNmPipe Waits for availability of named pipe instance Queue Management DosCloseQueue Closes queue (destroys if owner) DosCreateQueue Creates named queue DosOpenQueue Opens existing named queue DosPeekQueue Returns pointer to queue record without removing it from queue DosPurgeQueue Discards all records in queue DosQueryQueue Returns number of records in queue DosReadQueue Reads record and removes it from queue DosWriteQueue Writes record into queue Signal Management DosFlagProcess Sends event flag signal to another process DosHoldSignal Suspends signal processing for current process DosSendSignal Sends Ctrl-C or Ctrl-Break signal to another process DosSetSigHandler Registers signal handler Time, Date, and Timers DosGetDateTime Returns current time, date, and day of week DosSetDateTime Sets current date and time DosSleep Suspends requesting thread for specified interval DosTimerAsync Starts asynchronous one-shot timer DosTimerStart Starts asynchronous repeating timer DosTimerStop Stops timer Dynamic Linking DosFreeModule Releases handle for dynlink library DosGetModHandle Obtains handle for dynlink library DosGetModName Obtains filename of dynlink library DosGetProcAddr Obtains entry point for dynlink library routine DosLoadModule Loads dynlink library if it is not already loaded Device Monitors DosMonClose Terminates monitoring for character device DosMonOpen Returns handle for device monitoring DosMonRead Obtains monitor data packet DosMonReg Registers buffers for device monitor DosMonWrite Returns data packet to monitor chain Internationalization Support DosCaseMap Translates ASCII string in place DosGetCollate Returns collating sequence table DosGetCp Returns current code page identifier DosGetCtryInfo Returns internationalization information DosGetDBCSEv Returns table of double-byte character set codes DosPFSActivate Selects printer code page and font DosPFSCloseUser Notifies font switcher that spool file is closed DosPFSInit Initializes printer code page and font switching DosPFSQueryAct Returns printer code page and font ID DosPFSVerifyFont Checks existence of printer code page and font DosSetCp Selects code page for process and session DosSetProcCp Selects code page for process only Miscellaneous DosBeep Generates tone DosCallback Calls ring 3 procedure on behalf of ring 2 (IOPL) procedure DosCLIAccess Notifies intent to use CLI and STI DosDevConfig Returns system hardware configuration DosDevIOCtl Device-specific commands and information DosErrClass Returns information about error code DosError Disables or enables system critical error handler DosGetEnv Returns selector for process's environment DosGetInfoSeg Returns selectors for global and local read-only information segments DosGetMachineMode Returns flag indicating real or protected mode DosGetMessage Retrieves message from disk file DosGetVersion Returns OS/2 version number DosInsMessage Inserts variable text into body of message DosPortAccess Notifies intent to use range of I/O ports DosPutMessage Sends message to file, pipe, or device DosQAppType Returns application type (PM-aware, PM-compatible, etc.) DosQSysInfo Returns miscellaneous system information DosScanEnv Searches environment for string DosSetVec Registers handler for hardware exception Kernel API Functions DosAllocHuge [IOPL] [FAPI] Allocates a memory block of any size, limited only by the number of available selectors and the disk-swapping space, returning a base selector. The memory is movable and swappable. If the block is larger than one segment, it is addressed with logically contiguous selectors, and the increment between these selectors is calculated with the aid of DosGetHugeShift. A huge memory block is locked, unlocked, and freed as a unit by means of the base selector. WORD Number of complete 64 KB segments WORD Number of bytes in any partial last segment (0 = none) PTR WORD Receives base selector for huge memory block WORD Maximum number of 64 KB segments for subsequent DosReallocHuge (0 = block will not be expanded later) WORD Sharing option Bit(s) Significance (if set) 0 Shareable with DosGiveSeg 1 Shareable with DosGetSeg 2 Discardable 315 Reserved (0) Notes: A huge memory block may be shareable and/or discardable. If you use the discardable option, an implicit DosLockSeg is performed on the block when it is allocated. Huge memory blocks are discarded as a unit rather than on a segment-by-segment basis. In real mode, the function rounds the requested size up to the next paragraph (multiple of 16 bytes) and returns a physical segment address. The sharing/discardable parameter must be zero, and the maximum number of segments parameter is ignored. Related functions: DosAllocSeg, DosFreeSeg, DosGetHugeShift, DosGetSeg, DosGiveSeg, DosLockSeg, DosReallocHuge, DosSizeSeg, DosUnlockSeg. DosAllocSeg [IOPL] [FAPI] Allocates a memory segment with a maximum size of 65,536 bytes (64 KB). The memory is movable and swappable. WORD Size of segment in bytes (0 = 65,536) PTR WORD Receives selector WORD Sharing option Bit(s) Significance (if set) 0 Shareable with DosGiveSeg 1 Shareable with DosGetSeg 2 Discardable 315 Reserved (0) Notes: A segment may be shareable and/or discardable. If the discardable option is used, an implicit DosLockSeg is performed on the new block when it is allocated. In real mode, the function rounds the requested size up to the next paragraph (multiple of 16 bytes) and returns a physical segment address. The sharing/discardable parameter must be zero. Related functions: DosAllocHuge, DosAllocShrSeg, DosFreeSeg, DosGetSeg, DosGiveSeg, DosLockSeg, DosReallocSeg, DosSizeSeg, DosUnlockSeg. DosAllocShrSeg [IOPL] Allocates a named, shareable memory segment with a maximum size of 65,536 bytes (64 KB). The memory is swappable and movable. Any other process can obtain a selector for the same segment by calling DosGetShrSeg. WORD Size of segment in bytes (0 = 65,536) PTR ASCIIZ Segment name, in the form \SHAREMEM\name.ext PTR WORD Receives selector Related functions: DosAllocSeg, DosGetSeg, DosGetShrSeg, DosGiveSeg, DosSizeSeg. DosBeep [IOPL] [FAPI] Generates a tone of specified frequency and duration. The tone is synchronous; that is, the thread requesting DosBeep is suspended for the duration of the tone. WORD Frequency in Hertz (Hz), in the range 3732,767 WORD Duration in milliseconds (msec.) DosBufReset [IOPL] [FAPI] Flushes OS/2's internal buffers associated with a handle. If the handle is associated with a file, any unwritten data is physically written to disk, and the directory entry is updated. WORD File, pipe, or device handle (if handle = -1, all buffers for the process are written) Notes: [1.1] If a file or named pipe is opened with the "write-through" option, OS/2 does not defer writes by internally buffering data written to that handle. However, the directory entry for the file is not updated until the process calls DosClose or DosBufReset or terminates. [1.1] If a client process is reading data from a named pipe, DosBufReset blocks until all the data is read. Related functions: DosClose, DosMakeNmPipe, DosOpen, DosWrite. DosCallback [IOPL] [1.1] Calls a ring 3 function on behalf of code executing within a ring 2 (IOPL) segment. DWORD Address of ring 3 function Note: You cannot use the ring 2 stack to pass parameters to the ring 3 routine. Pass all parameters in registers. For a complete list of conforming functions, see p. 304. Related function: DosR2StackRealloc. DosCallNmPipe [1.1] Opens a named pipe, writes a message to the pipe, reads a message from the pipe, and then closes it. PTR ASCIIZ Pipe name, in the form \PIPE\name.ext for a local pipe or \\machine\PIPE\name.ext for a remote pipe PTR BUFFER Contains data to be written to pipe WORD Length of data to be written to pipe PTR BUFFER Receives data from pipe WORD Length of buffer to receive data from pipe PTR WORD Receives actual length of data read from pipe DWORD Timeout interval in milliseconds (0 = use default timeout; -1 = wait indefinitely) Notes: This function is used only by client processes. The pipe must be in message mode. If the buffer is too small for the message, the function fills the buffer and returns an error. To obtain the remaining data, you must make another call to DosCallNmPipe. Related functions: DosClose, DosMakeNmPipe, DosOpen, DosTransactNmPipe. DosCaseMap [FAPI] Translates a string of ASCII characters in place, according to the specified country and code page, using a case-mapping table obtained from the file COUNTRY.SYS. In general, lowercase characters are mapped to uppercase, and accented or otherwise modified vowels are changed to their plain vowel equivalents. WORD Length of string to case-map PTR BUFFER Country code data structure Offset Length Description 00H 2 Country code (0 = default) 02H 2 Code page ID (0 = default) PTR BUFFER Contains character string to case-map Note: In real mode, the COUNTRY.SYS file is assumed to be in the root directory of the current drive. Related functions: DosGetCollate, DosGetCp, DosGetCtryInfo, DosGetDBCSEv, DosSetProcCp, DosSetCp. DosChDir [IOPL] [FAPI] Selects the current directory for the current or specified drive. The current directory for each drive is maintained on a per-process basis and is inherited by child processes. PTR ASCIIZ Directory pathname (may include a drive identifier) DWORD Reserved (0) Related functions: DosQCurDir, DosQCurDisk, DosQSysInfo, DosSelectDisk. DosChgFilePtr [IOPL] [FAPI] Sets the read/write pointer associated with a file handle. The file pointer determines the starting location for the next read or write operation and is automatically incremented by these operations. When a file is opened, the initial file pointer location is zero (start of file). WORD File handle DWORD Signed file pointer offset WORD Method 0 Relative to start of file 1 Relative to current file pointer 2 Relative to end of file PTR DWORD Receives new absolute file pointer position Note: You can find the size of a file by calling DosChgFilePtr with offset = 0 and method = 2 and then examining the returned file pointer, or by calling DosQFileInfo. Related functions: DosOpen, DosRead, DosReadAsync, DosWrite, DosWriteAsync. DosCLIAccess [IOPL] [FAPI] Notifies the operating system that the process will disable and enable interrupts with the CLI and STI instructions. These instructions must be executed within an IOPL segment. No parameters Related function: DosPortAccess. DosClose [IOPL] [FAPI] Closes a handle for a file, pipe, or device, and releases the handle for reuse. If the handle was associated with a file, any unwritten data that is buffered within OS/2 is flushed to disk and the directory entry is updated if necessary. WORD Handle for file, pipe, or device Note: [1.1] A call to DosClose for a named pipe in the "connected" state by either client or server puts the pipe into "closing" state. If the pipe is in any other state and the server closes it, the pipe is destroyed. To make a pipe in the "closing" state available again, the server must call DosDisConnectNmPipe followed by DosConnectNmPipe. Related functions: DosBufReset, DosDupHandle, DosMakeNmPipe, DosOpen. DosCloseQueue Closes a queue. If the requesting process created the queue, the queue is destroyed. WORD Queue handle Related functions: DosCreateQueue, DosOpenQueue. DosCloseSem Closes a system semaphore. If there are no other open handles for the semaphore in the system, the semaphore is destroyed. DWORD Semaphore handle Related functions: DosCreateSem, DosOpenSem. DosConnectNmPipe [1.1] Waits for a client process to open an instance of a named pipe. WORD Pipe handle Notes: This function is used only by server processes. If the pipe is in blocking mode, DosConnectNmPipe waits if necessary for a client to open the pipe. If the pipe is in nonblocking mode, DosConnectNmPipe returns immediately with an error unless a client has already opened the pipe. Related functions: DosDisConnectNmPipe, DosMakeNmPipe, DosOpen. DosCreateCSAlias [IOPL] [FAPI] Returns an executable alias selector for a read-only or read/write data selector. Both the executable selector and the data selector refer to the same physical memory segment, but the executable selector can be loaded into the CS register whereas a data selector cannot. WORD Selector for data segment PTR WORD Receives executable selector Notes: The data selector must not refer to a shared or huge memory block. In protected mode, the segment persists until both the data and executable selectors are released with DosFreeSeg. In real mode, the two selectors are identical and are physical segment addresses; consequently, the memory block is deallocated when either selector is passed to DosFreeSeg. Related functions: DosAllocSeg, DosFreeSeg. DosCreateQueue Creates and opens a queue. The process that creates a queue is the queue owner and is the only process which can read records from the queue (although any process can write to a queue). PTR WORD Receives queue handle WORD Ordering method for queue records 0 First in, first out (FIFO) 1 Last in, first out (LIFO) 2 Priority (sender specifies priority 015) PTR ASCIIZ Queue name, in the form \QUEUES\name.ext Related functions: DosCloseQueue, DosOpenQueue. DosCreateSem [IOPL] Creates a named, global (system) semaphore. Other processes can subsequently obtain access to the semaphore with DosOpenSem. WORD Exclusive ownership option 0 Only owning process can modify semaphore 1 Any process can modify semaphore PTR DWORD Receives semaphore handle PTR ASCIIZ Semaphore name, in the form \SEM\name.ext Related functions: DosCloseSem, DosOpenSem. DosCreateThread [IOPL] Creates an asynchronous thread of execution within the same process. The new thread is assigned the same priority as the thread which created it. DWORD Initial execution address for new thread PTR WORD Receives thread ID DWORD Initial stack base for new thread Note: The stack grows downward, so the supplied base address must be the address of the last byte plus one allocated for the stack. Related functions: DosEnterCritSec, DosExit, DosExitCritSec, DosGetPrty, DosResumeThread, DosSetPrty, DosSuspendThread. DosCwait [IOPL] Waits for the termination of a child process and retrieves its termination type and exit code. The child process must have been started with DosExecPgm asynchronous option 2. WORD Child process subtree option 0 Wait for child process only 1 Wait for child process and all of its descendants WORD Wait option 0 Wait for child process to end 1 Return immediately with an error if the child process is still running; do not wait for child process to end PTR DWORD Receives termination information Offset Length Description 00H 2 Termination type 0 = normal termination (using DosExit) 1 = critical error abort 2 = execution fault (such as GP fault) 3 = DosKillProcess signal 02H 2 Child's exit code (from DosExit) PTR WORD Receives PID of terminating process WORD PID of child process to wait for (0 = wait until any child process ends and return its PID) Related functions: DosExecPgm, DosExit, DosKillProcess. DosDelete [IOPL] [FAPI] Deletes a file by releasing its storage for reuse and removing its directory entry. You cannot delete read-only files or files that are currently opened by another process. PTR ASCIIZ Pathname of file to be deleted (cannot contain wildcards) DWORD Reserved (0) Related functions: DosQFileMode, DosRmDir, DosSetFileMode. DosDevConfig [IOPL] [FAPI] Returns the machine model and configuration. PTR BYTE Receives device information WORD Type of information needed 0 Number of printers attached 1 Number of serial ports 2 Number of floppy disk drives 3 Presence of math coprocessor (returns 0 = not present, 1 = present) 4 PC submodel type 5 PC model type 6 Primary display adapter type (returns 0 = monochrome adapter, 1 = other) WORD Reserved (0) Related function: VioGetConfig. DosDevIOCtl [IOPL] [FAPI] Returns device-specific information or allows a program to issue device-specific commands that are not supported by other API functions. PTR BUFFER Receives subfunction-specific data PTR BUFFER Contains subfunction-specific parameters WORD Function number WORD Category number 1 Serial port 2 Reserved 3 Pointer device (mouse) 4 Keyboard 5 Printer 6 Light pen 7 Mouse 8 Logical disk 9 Physical disk 10 Character device monitor 11 General device control 12127 Reserved for OS/2 128255 Available for user drivers and programs WORD Device handle Notes: The arguments and returned results for each DosDevIOCtl subfunction are documented in Part 2 of this reference section, which begins on p. 621. Category 7 functions use a handle obtained from MouOpen, and Category 9 functions use a handle supplied by DosPhysicalDisk. Handles for use with the remaining functions can be obtained from DosOpen or inherited from the parent process. In real mode, only a restricted set of category 1, 5, and 8 subfunctions are supported. Related function: DosOpen. DosDisConnectNmPipe [1.1] Unilaterally breaks a named pipe connection between a server and a client; in so doing, denies a client process any further access to it. Any data waiting in the pipe is discarded. WORD Pipe handle Notes: This function is used only by server processes. The client process should close the associated pipe handle even though it cannot use the pipe after the server has disconnected it. Related functions: DosClose, DosConnectNmPipe. DosDupHandle [IOPL] [FAPI] Duplicates a handle and returns a new handle that refers to the same file, pipe, or device; alternatively, redirects an existing handle to refer to the same file, pipe, or device as another existing handle. If duplicate handles refer to a file, they have the same file pointer, and any sharing and access restrictions on the original handle are also duplicated. WORD Existing file, pipe, or device handle to be duplicated PTR WORD Contains handle to be redirected, or -1 to obtain a new handle; in the latter case, receives the new handle in the same location Related functions: DosChgFilePtr, DosClose, DosMakeNmPipe, DosMakePipe, DosRead, DosWrite. DosEnterCritSec [IOPL] Disables execution of all other threads within the same process. You can reactivate the other threads with DosExitCritSec. Typically, this function is used to protect a thread's access to a variable which is also used by other threads. No parameters Notes: DosEnterCritSec calls can be nested, requiring an equivalent number of DosExitCritSec calls before the other threads are released. This function does not disable signal processing, which is still performed using the process's primary thread. Avoid making calls to other kernel API functions within a DosEnterCritSec ... DosExitCritSec sequence because some of these calls create additional threads on behalf of the process. Related functions: DosCreateThread, DosExitCritSec, DosResumeThread, DosSuspendThread. DosErrClass [IOPL] [FAPI] Returns an error class, recommended recovery action, and device type (locus) for an API error code. WORD Error code for analysis PTR WORD Receives error class 1 Out of resources 2 Temporary situation 3 Authorization failed 4 Internal error in system software 5 Hardware failure 6 System software failure not fault of active process 7 Probable application error 8 File or item not found 9 File or item of invalid type or format 10 File or resource locked 11 Incorrect media or CRC error 12 Resource already exists or already committed, or action previously performed 13 Unclassified 14 Can't perform requested action 15 Timeout PTR WORD Receives recommended recovery action 1 Retry immediately 2 Delay and retry 3 Get corrected information from user 4 Terminate with cleanup 5 Terminate immediately without cleanup 6 Ignore error 7 Retry after user intervention to remove cause of error PTR WORD Receives error locus 1 Unknown 2 Block device 3 Network 4 Serial device 5 Memory Note: Returned values might not be the same in real and protected mode. Related function: DosError. DosError [IOPL] [FAPI] Notifies the operating system that the process will handle unrecoverable critical (hardware-related) errors, or restores the handling of these errors to the operating system. Can also be used to disable notification pop-ups for CPU exceptions (faults), such as general protection faults or divide by zero, although the handling of such exceptions is not affected. WORD Critical error processing option Bit(s) Significance 0 0 = suspend system critical error processing; return errors to the requesting process 1 = resume system critical error processing 1 0 = enable exception (for example, divide by zero) notification popups 1 = disable exception pop-ups 215 Reserved (0) Notes: An exception terminates the process unless it has installed its own handler with DosSetVec. GP faults always terminate the process. You can disable critical error handling by the system for a particular file or device by setting the error mode bit with DosOpen or DosSetFHandState. In real mode, calling DosError with processing option = 0 causes the system's Int 24H (critical error) handler to return a "fail" code until you call DosError again with processing option = 1. Related functions: DosErrClass, DosOpen, DosSetFHandState, DosSetVec. DosExecPgm [IOPL] [FAPI] Starts a child process within the same session. The child inherits the parent process's environment and file, pipe, and device handles unless the parent takes special action to prevent it. The parent can check the child process's status with DosCwait or terminate it with DosKillProcess. PTR BUFFER Object name buffer WORD Length of object name buffer WORD Child process execution mode 0 Synchronous 1 Asynchronous, no DosCwait 2 Asynchronous, subsequent DosCwait 3 Traceable (see DosPTrace) 4 Asynchronous detached (orphan) 5 Frozen PTR ASCIIZ One or more ASCIIZ argument strings to be passed to child process; terminate entire set with an extra null byte (use null pointer if no argument strings) PTR ASCIIZ One or more ASCIIZ environment strings to be passed to child process; terminate entire set with an extra null byte (use null pointer to pass copy of current process's environment) PTR DWORD Receives child process information (see Notes) PTR ASCIIZ Pathname of child process's executable file Notes: For synchronous child processes (execution mode 0), the returned information is as follows: Offset Length Description 00H 2 Child termination type 0 = normal termination (using DosExit) 1 = critical error abort 2 = execution fault (such as GP fault) 3 = DosKillProcess signal 02H 2 Child exit code (from DosExit) For asynchronous child processes (execution modes 1 and 2), the returned information is as follows: Offset Length Description 00H 2 Child process ID 02H 2 Nothing For child execution mode 2, the child process's termination type and exit code are collected with a subsequent call to DosCwait. Memory is consumed if DosCwait is not called; if the information is not needed, use child execution mode 1 instead. If the DosExecPgm call fails because of a missing dynlink module or entry point, the corresponding name is returned as an ASCIIZ string in the object name buffer. If the child program's pathname contains neither a drive nor a path element (i.e., no : or \ delimiters), the current directory and then each directory in the parent's PATH variable is searched for the executable file. The file's extension must be explicit. For compatibility with CMD.EXE, the argument strings should consist of the simple name of the program followed by the command tail, for example: db 'MEP',0 db ' MYFILE.DAT',0 db 0 In real mode, child process execution modes 1 through 5 are not supported. Related functions: DosCwait, DosExit, DosKillProcess, DosPTrace, DosStartSession. DosExit [IOPL] [FAPI] Terminates a thread or process. When DosExit is used to terminate a process, the exit code can be collected by the parent process with DosCwait. WORD Action code 0 Terminate current thread only 1 Terminate process WORD Exit (result) code Notes: If the action code is 0, but the requesting thread is the only thread or the last active thread in a process, the process is terminated. If termination routines have been registered with DosExitList, the primary thread is reactivated and used to execute each of them. All of the process's resources (memory, files, semaphores, etc.) are then freed. [1.0] Because the primary thread (thread 1) is used for signal handling, it should never be terminated unless the process is also terminated. [1.1] Termination of the primary thread always terminates the process, regardless of the value of the action code parameter. In real mode, threads are not supported, and this function always terminates the process. Related functions: DosCwait, DosExecPgm, DosExitList. DosExitCritSec [IOPL] Enables execution of other threads within the same process; cancels a previous DosEnterCritSec call. No parameters. Note: If you nest calls to DosEnterCritSec, you must make an equivalent number of DosExitCritSec calls before the other threads are released. Related functions: DosCreateThread, DosEnterCritSec, DosResumeThread, DosSuspendThread. DosExitList [IOPL] Maintains a list of procedures (sometimes called ExitList routines) that are called by the kernel when the current process terminates. These routines are executed before any resources still owned by the process (memory, files, semaphores, etc.) are freed by the kernel. WORD Request type 1 Add ExitList routine 2 Remove ExitList routine 3 Processing complete (used by ExitList routines only) DWORD Address of ExitList routine (ignored for request type 3) Notes: The ExitList routines are not guaranteed to execute in any particular order. Each ExitList routine uses the process's original stack, runs as the primary thread (thread 1), and must exit by calling DosExitList with request type 3. When an ExitList routine receives control, the reason for termination is on the stack at SS:SP + 4: 0 = normal termination (using DosExit) 1 = critical error abort 2 = execution fault (such as GP fault) 3 = DosKillProcess signal An ExitList routine cannot call DosExecPgm or DosCreateThread. Related functions: DosCreateThread, DosExecPgm, DosExit, DosKillProcess, DosSetSigHandler. DosFileLocks [IOPL] [FAPI] Locks and/or unlocks a file region. While a region is locked, other processes are not allowed to read or write the region. In this way, data integrity is maintained when multiple processes are using the same file. If a lock operation fails, some other process has already locked the same file region, and the current process should delay and try again, notify the user, or take some other appropriate recovery action. WORD File handle PTR BUFFER Contains description of region to be unlocked (see Notes) PTR BUFFER Contains description of region to be locked (see Notes) Notes: The buffers that describe a file region are 8 bytes long: Offset Length Description 00H 4 File offset 04H 4 Region length If you pass a null (zero) pointer for either buffer describing a file region, the corresponding action is omitted. Locking beyond the current end of file is not an error. The unlock range, if present, is processed before the lock range. Avoid locking regions that overlap. All access to a file by other processes can be blocked by opening the file with the deny-all sharing mode. In such cases, additional calls to DosFileLocks are not needed. When the process terminates, any remaining locks are released and the file is closed. Access to locked regions is not inherited by child processes. Related functions: DosExecPgm, DosOpen. DosFindClose [IOPL] [FAPI] Closes a directory search handle. WORD Directory search handle from DosFindFirst Related functions: DosFindFirst, DosFindNext. DosFindFirst [IOPL] [FAPI] Returns the first matching pathname, if any, and a directory search handle. If a match is found, information from the directory, such as the file's size, attributes, and time and date stamps, is returned; if no match is found, the function returns an error. PTR ASCIIZ Pathname to search for (may include wildcard characters) PTR WORD Contains -1 to allocate a new search handle, or 1 (a handle that is always available); in the former case, receives the new handle in the same location WORD Search attribute (bits may be combined) Bit(s) Significance (if set) 0 Read-only 1 Hidden 2 System 3 Reserved (0) 4 Directory 5 Archive 615 Reserved (0) PTR BUFFER Receives search results (minimum length 36 bytes; see Notes) WORD Length of buffer that receives search results PTR WORD Contains maximum number of matches requested; receives the actual number of matches DWORD Reserved (0) Notes: An attribute of 0 selects only normal files that match the pathname. When one or more attribute bits are set, the function returns all matching normal files as well as all matching files that have any of the same attributes. You can obtain multiple matches with one function call. The information for each matched file is packed into the result buffer in consecutive records with the following format: Offset Length Description 00H 2 File date of creation 02H 2 File time of creation 04H 2 File date of last access 06H 2 File time of last access 08H 2 File date of last write 0AH 2 File time of last write 0CH 4 File size 10H 4 File allocation 14H 2 File attribute 16H 1 Length of filename (n) 17H+ n+1 ASCIIZ filename (does not include drive or path) The date and time of file creation and last access are zero in FAT file systems. Note that the length of each record within the result buffer depends on the filename and is not a constant. The number of matches returned is the minimum of the number requested, the number which will fit into the result buffer, and the number which actually exist. The time fields are formatted as follows: Bits Description 04 2-second increments (029) 510 Minutes (059) 1115 Hours (023) The date fields are formatted as follows: Bits Description 04 Day (131) 58 Month (112) 915 Year (relative to 1980) Directory search handles are not assigned from the same sequence as file, pipe, and device handles. Approximately 1000 search handles are available per process (memory permitting). In real mode, you can pass a directory handle of 1 or -1 on the first call to DosFindFirst, and a handle of 1 must always be used thereafter. In other words, only one search may be active at a time. Related functions: DosFindClose, DosFindNext, DosQFileInfo, DosSearchPath. DosFindNext [IOPL] [FAPI] Finds the next match, if any, for an ambiguous pathname passed previously to DosFindFirst. WORD Directory search handle from previous DosFindFirst PTR BUFFER Receives search results (see DosFindFirst) WORD Length of buffer that receives search results PTR WORD Contains maximum number of matches requested; receives the actual number of matches Notes: Use of this function presumes a previous DosFindFirst that returned at least one matching file. If there are no more matching files, an error is returned. In real mode, the directory handle must always be 1. Related functions: DosFindClose, DosFindFirst. DosFlagProcess [IOPL] Sends an event flag signal to another process or to a process and all its descendants. The destination process must be either the current process or a nondetached child process. The signal is ignored unless the receiving process has registered a handler for it with DosSetSigHandler. WORD PID of destination process WORD Destination type 0 Process and all its descendants 1 Designated process only WORD Event flag number 0 Flag A 1 Flag B 2 Flag C WORD Private data (passed to destination process, ignored by OS/2) Note: The destination process need not still be alive for its own descendants to receive the signal. Related functions: DosHoldSignal, DosSendSignal, DosSetSigHandler. DosFreeModule [IOPL] Closes a dynlink library handle. After all active handles for a library are released, the memory occupied by the library is released. WORD Dynlink library handle Related functions: DosGetModHandle, DosGetModName, DosGetProcAddr, DosLoadModule. DosFreeSeg [IOPL] [FAPI] Releases a selector for a memory block previously allocated with DosAllocHuge, DosAllocSeg, or DosAllocShrSeg. You can also use this function to release a selector obtained from DosCreateCSAlias, DosGetSeg, VioGetPhysBuf, etc., or to release a "giveaway" selector passed by another process. When all the selectors owned by all processes for a particular memory block have been freed, physical memory and swap file space are reclaimed. WORD Selector Note: In protected mode, the data selector used as an argument to DosCreateCSAlias and the returned executable selector are distinct, and you can free either one without releasing the associated memory block. In real mode, the two segment addresses are the same, and freeing either one also releases the memory. Related functions: DosAllocHuge, DosAllocSeg, DosAllocShrSeg, DosCreateCSAlias. DosFSRamSemClear [IOPL] [1.1] Releases ownership of a fast-safe RAM semaphore. PTR BUFFER Control structure for fast-safe RAM semaphore, in the following format: Offset Length Description 00H 2 Contains length of structure (14 bytes in OS/2 version 1.1) 02H 2 Receives process ID of owner (0 = semaphore unowned) 04H 2 Receives thread ID of owner (0 = semaphore unowned) 06H 2 Receives semaphore reference count 08H 2 Field for use by semaphore owner 0AH 4 Storage for semaphore The requesting process should not modify shaded fields. Related functions: DosExitList, DosFSRamSemRequest, DosSemClear. DosFSRamSemRequest [IOPL] [1.1] Obtains ownership of a fast-safe RAM semaphore or waits until the semaphore is available. The system saves information about the current semaphore owner for possible use by an ExitList cleanup routine. As a consequence, these semaphores are particularly appropriate for use by dynlink libraries with multiple client processes. PTR BUFFER Control structure for fast-safe RAM semaphore, in the following format: Offset Length Description 00H 2 Contains length of structure (14 bytes in OS/2 1.1) 02H 2 Receives process ID of owner (0 = semaphore unowned) 04H 2 Receives thread ID of owner (0 = semaphore unowned) 06H 2 Receives semaphore reference count 08H 2 Field for use by semaphore owner 0AH 4 Storage for semaphore The requesting process should not modify shaded fields. DWORD Timeout interval in milliseconds (0 = return immediately with an error if semaphore is owned; -1 = wait indefinitely) Notes: Nested calls to DosFSRamSemRequest are supported. The semaphore is not cleared, thereby releasing any blocked threads, until the number of calls to DosFSRamSemClear matches the number of requests. An ExitList procedure (registered with DosExitList) can call DosFSRamSemRequest for a fast-safe RAM semaphore that controls a resource. If the current owner of that semaphore was any thread in the same process, the DosFSRamSemRequest succeeds and the thread ID and reference count fields of the semaphore are forced to 1. The ExitList routine can then take any necessary measures to clean up the resource symbolized by the semaphore and call DosFSRamSemClear to release the semaphore. Related functions: DosExitList, DosFSRamSemClear, DosSemRequest. DosGetCollate [FAPI] Obtains a character-collating table for the specified country and code page from the COUNTRY.SYS file. The collating table assigns sorting weights for each ASCII character; typically, uppercase and lowercase characters are given the same weight so that sorts will be case insensitive, and accented or otherwise modified vowels are given the same weight as their plain vowel equivalents. WORD Length of buffer to receive collating table PTR BUFFER Country code data structure Offset Length Description 00H 2 Country code (0 = default) 02H 2 Code page ID (0 = default) PTR BUFFER Receives collating table (truncated if necessary to fit within buffer, or padded with zero bytes if buffer is larger than table) PTR WORD Receives actual length of collating table Notes: The maximum table size is 256 bytes. In real mode, the COUNTRY.SYS file is assumed to be in the root directory of the current drive. Related functions: DosCaseMap, DosGetCp, DosGetCtryInfo, DosGetDBCSEv, DosSetCp, DosSetProcCp. DosGetCp [IOPL] [FAPI] Obtains the current code page and a list of the other available ("prepared") code pages. The current code page is inherited from the parent process and can be changed with DosSetCp or DosSetProcCp. WORD Length of buffer to receive code page list PTR BUFFER Receives code page list (truncated if necessary) Offset Length Description 00H 2 Current code page ID 02H 2 Alternate code page ID #1 04H 2 Alternate code page ID #2 . . . . . . . . . PTR WORD Receives actual length of code page list Note: In real mode, the function returns no more than one current code page and one prepared code page. Related functions: DosCaseMap, DosGetCollate, DosGetCtryInfo, DosGetDBCSEv, DosSetCp, DosSetProcCp, KbdGetCp, VioGetCp. DosGetCtryInfo [FAPI] Obtains country-dependent formatting information from the file COUNTRY.SYS. WORD Length of buffer to receive country information (38 bytes is sufficient in OS/2 versions 1.0 and 1.1) PTR BUFFER Country code data structure Offset Length Description 00H 2 Country code (0 = default) 02H 2 Code page ID (0 = default) PTR BUFFER Receives country information (truncated if necessary; see Notes) PTR WORD Receives actual length of country information Notes: The format of the returned country information is as follows: Offset Length Description 00H 2 Country code 02H 2 Code page ID 04H 2 Date format 0 = mm/dd/yy 1 = dd/mm/yy 2 = yy/mm/dd 06H 5 ASCIIZ currency symbol 0BH 2 ASCIIZ thousands separator 0DH 2 ASCIIZ decimal separator 0FH 2 ASCIIZ date separator 11H 2 ASCIIZ time separator 13H 1 Bit field for currency format: Bit(s) Significance 0 0 = currency symbol precedes value 1 = currency symbol follows value 1 Spaces between currency symbol and value 2 0 = no effect 1 = ignore bits 0 and 1, currency symbol replaces decimal separator 37 Reserved (0) 14H 1 Number of decimal places in currency 15H 1 Time format Bit(s) Significance 0 0 = 12-hour format with "a" or "p" 1 = 24-hour format 17 Reserved (0) 16H 4 Reserved (0) 1AH 2 ASCIIZ data list separator 1CH 10 Reserved (0) In real mode, the COUNTRY.SYS file is assumed to be in the root directory of the current drive. If an FAPI program is running under MS-DOS, some country information might be unavailable. Related functions: DosGetCp, DosSetCp, DosSetProcCp. DosGetDateTime [IOPL] [FAPI] Obtains the current date, time, time zone, and day of the week. PTR BUFFER Receives date and time information in the following format: Offset Length Description 00H 1 Hour (023) 01H 1 Minute (059) 02H 1 Second (059) 03H 1 Hundredths of a second (099) 04H 1 Day (131) 05H 1 Month (112) 06H 2 Year (19802079) 08H 2 Time zone (minutes GMT, 300 = U.S. EST, -1 = undefined) 0AH 1 Day of the week (0 = Sunday, 1 = Monday, etc.) Related functions: DosGetInfoSeg, DosSetDateTime. DosGetDBCSEv [FAPI] Obtains a table of ranges for double-byte character set (DBCS) codes from the file COUNTRY.SYS. WORD Length of buffer to receive table (10 bytes is adequate in OS/2 versions 1.0 and 1.1) PTR BUFFER Country code data structure Offset Length Description 00H 2 Country code (0 = default) 02H 2 Code page ID (0 = default) PTR BUFFER Receives DBCS code table (see Notes) Notes: The DBCS table consists of 2-byte entries that define ranges for DBCS lead bytes. The table is terminated by a pair of zero bytes, unless it must be truncated to fit within the buffer. For example: db 81h,9fh db 0e0h,0fch db 0,0 In real mode, the COUNTRY.SYS file is assumed to be in the root directory of the current drive. Related functions: DosGetCp, DosGetCtryInfo, DosSetCp, DosSetProcCp. DosGetEnv [IOPL] [FAPI] Returns the selector for the process's environment, and the offset to the argument strings (command line information) at the end of the environment. PTR WORD Receives selector for environment PTR WORD Receives offset of argument strings at end of environment Note: When a program is started by CMD.EXE, the argument strings consist of the ASCIIZ simple name of the program, followed by the ASCIIZ command tail, terminated by an additional null byte. Related functions: DosExecPgm, DosScanEnv, DosStartSession. DosGetHugeShift [IOPL] [FAPI] Returns a left shift count that is applied to the value 1 to obtain an incrementing value. The consecutive selectors for a huge memory block are calculated by applying this increment to the base selector returned by DosAllocHuge. You can also obtain the shift count from the global information segment. PTR WORD Receives shift count Related functions: DosAllocHuge, DosGetInfoSeg, DosReallocHuge. DosGetInfoSeg [IOPL] Returns selectors for the global and local (process-specific) read-only information segments. PTR WORD Receives selector for global information segment (see Notes) PTR WORD Receives selector for local information segment (see Notes) Notes: The global information segment has a fixed selector in the GDT and is read-only to user processes. Its format is as follows: Offset Length Description 00H 4 Elapsed seconds from January 1, 1970 04H 4 Milliseconds since system was turned on or restarted 08H 1 Hour 09H 1 Minute 0AH 1 Second 0BH 1 Hundredths of a second 0CH 2 Time zone (minutes GMT, -1 if undefined) 0EH 2 Timer tick interval (units = 0.0001 seconds; value is 310 in OS/2 versions 1.0 and 1.1) 10H 1 Day (131) 11H 1 Month (112) 12H 2 Year (19802079) 14H 1 Day of week (0 = Sunday, 1 = Monday, etc.) 15H 1 OS/2 major version number 16H 1 OS/2 minor version number 17H 1 OS/2 revision letter, or zero 18H 1 Current foreground screen group 19H 1 Maximum number of screen groups 1AH 1 Shift count for huge segments 1BH 1 Protected modeonly indicator (0 = 3.x Box available, 1 = 3.x Box disabled) 1CH 2 PID of the last process to call KbdCharIn or KbdGetFocus in foreground screen group 1EH 1 Dynamic priority variation flag (0 = disabled, 1 = enabled) 1FH 1 Maximum wait (seconds) 20H 2 Minimum timeslice (msec.) 22H 2 Maximum timeslice (msec.) 24H 2 Boot drive (1 = A, 2 = B, etc.) 26H 32 System trace flags. Each bit corresponds to a trace major event code from 00H through 0FFH. If a bit is 1, tracing of the corresponding event is enabled. 46H 1 Maximum number of Vio windowable applications for Presentation Manager screen group (version 1.1) 47H 1 Maximum number of Presentation Manager applications in PM screen group (version 1.1) The local information segment has a fixed selector in every process's LDT and is read-only. Its format is as follows: Offset Length Description 00H 2 Current process ID 02H 2 PID of parent 04H 2 Priority of current thread 06H 2 Current thread ID 08H 2 Screen group 0AH 2 Subscreen group 0CH 2 In-foreground flag (-1 if current process has keyboard focus) 0EH 1 Process type 0 = full screen application 1 = real mode process 2 = Vio windowable application 3 = Presentation Manager application 4 = detached application 0FH 1 Reserved 10H 2 Environment selector (version 1.1) 12H 2 Command line offset within environment (version 1.1) 14H 2 Initial size of DGROUP (version 1.1) 16H 2 Initial size of process's default stack (version 1.1) 18H 2 Initial heap size (version 1.1) 1AH 2 Module handle (version 1.1) 1CH 2 DGROUP selector (version 1.1) [1.1] The last seven fields in the local information segment correspond to the values in registers AX, BX, CX, DX, SI, DI, and DS respectively at the initial entry to a process. Related functions: DosGetDateTime, DosGetEnv, DosGetHugeShift, DosGetPID, DosGetPPID, DosGetPrty, DosGetVersion, DosQAppType. DosGetMachineMode [IOPL] [FAPI] Returns a code for the current processor mode (real or protected). FAPI applications can use this information to adjust their use of dynlink calls when loaded in real mode. PTR BYTE Receives current machine mode 0 Real mode 1 Protected mode Related functions: DosDevConfig, DosGetInfoSeg, DosGetVersion, VioGetConfig. DosGetMessage [FAPI] Retrieves a message from the message segment in the program's EXE file or from a system message file, optionally inserting text into variable fields of the message body. PTR BUFFER Contains 09 far (DWORD) pointers to ASCIIZ strings that function inserts into variable fields of message WORD Number of strings to insert (09) PTR BUFFER Receives message text from file, with strings inserted, truncated if necessary WORD Length in bytes of buffer to receive message text WORD Number of message in file PTR ASCIIZ Pathname of message file PTR WORD Receives actual length in bytes of message, including any variable text that was inserted into message Notes: The insertion points for variable strings are indicated by tokens in the form %%@AI@%n, such as %1, %2, etc. If the program has no message segment and the pathname of the message file does not contain : or \ delimiters, the current directory, root directory, and any directories specified by DPATH (protected mode) or APPEND (real mode) are searched for the file. Related functions: DosInsMessage, DosPutMessage. DosGetModHandle [IOPL] Returns a module handle for a dynlink library if that library is already loaded. The use count for the module is not incremented. PTR ASCIIZ Module name of dynlink library PTR WORD Receives module handle Note: The module name is specified in the DLL file header and is usually the same as the filename. Related functions: DosFreeModule, DosGetModName, DosGetProcAddr, DosLoadModule. DosGetModName [IOPL] Given a module handle for a dynlink library, returns the fully qualified pathname (drive, path, filename, and extension) for the corresponding DLL file. Can also be used to obtain the pathname for a process's EXE file, although this use is uncommon. WORD Module handle WORD Length in bytes of buffer to receive pathname PTR BUFFER Receives pathname Note: A dynlink library's handle is passed to its initialization routine in register AX. In contrast, a process's module handle is passed to it in register DI. Related functions: DosFreeModule, DosGetModHandle, DosGetProcAddr, DosLoadModule. DosGetPID [IOPL] Obtains the process ID of the current process, thread ID of the current thread, and the process ID of the process's parent. PTR BUFFER Receives information in the following form: Offset Length Description 00H 2 PID of current process 02H 2 Thread ID of current thread 04H 2 PID of parent process Related functions: DosExecPgm, DosGetInfoSeg, DosGetPPID, DosStartSession. DosGetPPID [IOPL] [1.1] Obtains the process ID of the parent of any process. WORD PID of process of interest PTR WORD Receives PID of parent process Related functions: DosExecPgm, DosGetInfoSeg, DosGetPID, DosStartSession. DosGetProcAddr [IOPL] Obtains the selector and offset of the entry point for a dynlink library routine. A process can use DosGetProcAddr for selective dynamic linking at run time, rather than relying on the kernel's loader to establish all dynamic links at load time. WORD Module handle for dynlink library PTR ASCIIZ ASCIIZ name of library procedure PTR DWORD Receives selector and offset of entry point Notes: Instead of the procedure name, the ASCIIZ string can contain the ordinal of the procedure with # as the first character, such as: db '#8',0 Alternatively, if the selector portion of the pointer to the name is zero, the offset portion is interpreted as the binary ordinal. Most Dos API functions cannot be dynamically linked by name at run time, and must be referenced by their ordinals. To obtain these ordinals, inspect the import library (either DOSCALLS.LIB or OS2.LIB). Note that the ordinals required by DosGetProcAddr are one-based, whereas the ordinals in import libraries are zero-based. Related functions: DosFreeModule, DosGetModHandle, DosGetModName, DosLoadModule. DosGetPrty [IOPL] Returns the priority of any thread within the current process or of the primary thread in another process. WORD Scope of inquiry 0 Primary thread of specified process 2 Thread within the current process PTR BUFFER Receives priority information in a 2-byte structure: Offset Length Description 00H 1 Priority level within class (031) 01H 1 Priority class 1 = idle-time 2 = regular 3 = time-critical WORD Process or thread ID (0 = current) Note: [1.0] If the scope is 0 and the primary thread of the indicated process has terminated, an error is returned even if the process is still running. Related functions: DosCreateThread, DosSetPrty. DosGetResource [IOPL] [1.1] Returns a selector for a resource. Resources are read-only data segments that contain icons, bitmaps, strings, or other data that are bundled into an EXE or DLL file with the resource compiler (RC.EXE). WORD Module handle WORD Resource type ID WORD Resource name ID PTR WORD Receives selector for resource Notes: The module handle must be previously obtained with DosLoadModule or DosGetModHandle. If the module handle is zero, the resource is loaded from the current process's EXE file. The resource type and name identifiers are 16-bit values assigned to a resource when it is created. Although many resources can have the same type ID, the combination of the type ID and name ID for a particular resource must be unique. Related functions: DosGetModHandle, DosLoadModule. DosGetSeg [IOPL] Given a selector for a "gettable" segment, makes that segment addressable for the current process. The selector must be passed to the current process by the process that originally allocated it, using some form of interprocess communication. WORD Selector Note: If the segment was originally allocated with the discardable option, the current process can unlock it for discard by calling DosUnlockSeg. Locking is an attribute of a memory segment and is not associated with the processes using the segment. Related functions: DosAllocHuge, DosAllocSeg, DosFreeSeg, DosGiveSeg. DosGetShrSeg [IOPL] Obtains a selector for a named, shareable memory segment. The segment must have been previously created with DosAllocShrSeg. When all of the selectors owned by all of the processes for a named segment have been released with DosFreeSeg, the segment is destroyed. PTR ASCIIZ Segment name, in the form \SHAREMEM\name.ext PTR WORD Receives selector Related functions: DosAllocShrSeg, DosFreeSeg. DosGetVersion [IOPL] [FAPI] Obtains the OS/2 version number. PTR BUFFER Receives version information in a 2-byte structure: Offset Length Description 00H 1 Minor version number (00H for OS/2 version 1.0 and 0AH for OS/2 version 1.1) 01H 1 Major version number (0AH for OS/2 versions 1.0 and 1.1) Note: The major version number is 10-based so that a bound Family application can distinguish between the OS/2 DOS compatibility box and an MS-DOS 1.x environment. Related functions: DosDevConfig, DosGetInfoSeg, DosGetMachineMode, VioGetConfig. DosGiveSeg [IOPL] Obtains a new selector, which can be used by another process, for a segment previously allocated with the "givable" option. The selector must be passed to the other process using some form of interprocess communication. WORD Selector, valid for the current process, for the segment to be shared WORD PID of process that will use new selector PTR WORD Receives new selector, which is valid for other process Note: If the segment was originally allocated with the discardable option, the process receiving the giveaway selector can unlock it for discard by calling DosUnlockSeg. Locking is an attribute of a memory segment and is not associated with the processes using the segment. Related functions: DosAllocHuge, DosAllocSeg, DosFreeSeg, DosGetSeg. DosHoldSignal [IOPL] [FAPI] Enables or disables signal processing for the current process. SIGTERM signals are not affected. If you nest calls to DosHoldSignal to disable signal processing, you must make a corresponding number of enabling calls before signals are again received by the process. WORD Action code 0 Enable signal processing 1 Disable signal processing Notes: Signals which occur while signal processing is disabled are held until processing is reenabled. In real mode, only the signals SIGINTR (Ctrl-C) and SIGBREAK (Ctrl-Break) are recognized. (Both are treated as SIGINTR.) Related functions: DosFlagProcess, DosKillProcess, DosSendSignal, DosSetSigHandler. DosInsMessage [FAPI] Inserts text into the variable fields of a message. PTR BUFFER Contains 09 far (DWORD) pointers to ASCIIZ strings that the function inserts into variable fields of message WORD Number of strings to insert (09) PTR BUFFER Contains message to be processed WORD Length of message to be processed PTR BUFFER Receives expanded message WORD Length of buffer to receive expanded message PTR WORD Receives actual length of expanded message Note: The insertion points for variable strings are indicated by tokens in the form %%@AI@%n, such as %1, %2, etc. Related functions: DosGetMessage, DosPutMessage, DosWrite. DosKillProcess [IOPL] Terminates another process and (optionally) all its child processes by sending a SIGTERM signal. A process can protect itself against such signals with DosSetSigHandler. WORD Action code 0 Designated process and all its descendants 1 Designated process only WORD PID of process to be terminated Related functions: DosCwait, DosExit, DosSetSigHandler, DosStopSession. DosLoadModule [IOPL] Loads a dynlink library, if it is not already loaded, and returns a module handle for the library. The handle may be used with DosGetProcAddr to obtain the addresses of procedures within the library. PTR BUFFER Object name buffer WORD Length of object name buffer PTR ASCIIZ Name of dynlink library PTR WORD Receives module handle Notes: Supply only the name of the library file, without a path or extension. The extension is always assumed to be DLL, and the location of the library is specified by the LIBPATH directive in the CONFIG.SYS file. If the DosLoadModule call fails because of a missing dynlink module or entry point, the corresponding name is returned as an ASCIIZ string in the object name buffer. Related functions: DosFreeModule, DosGetModHandle, DosGetModName, DosGetProcAddr, DosGetResource. DosLockSeg [IOPL] Notifies the operating system that a segment should not be discarded, returning an error if the segment has already been discarded; reverses the effect of a previous DosUnlockSeg call. The segment is still swappable and movable while it is locked. Used only with segments that were allocated with the discardable option (bit 2). You can nest calls to DosLockSeg to a maximum of 255, at which point the segment becomes permanently locked. WORD Selector Notes: If the segment has been discarded, the selector is still valid. You must reallocate the block with DosReallocSeg or DosReallocHuge (which also lock the segment) and then reload or regenerate the data in the segment. A segment may be locked or unlocked by any process that owns a selector for it. Locking is an attribute of a segment and not of the processes using the segment. Related functions: DosAllocHuge, DosAllocSeg, DosReallocHuge, DosReallocSeg, DosUnlockSeg. DosMakeNmPipe [1.1] Creates and opens a named pipe or a new instance of a named pipe, and returns a handle that you can use for subsequent access to the pipe. The process that creates a named pipe is the server and is the only process that can issue DosConnectNmPipe and DosDisConnectNmPipe calls. PTR ASCIIZ Pipe name, in the form \PIPE\name.ext for a local pipe or \\machine\PIPE\name.ext for a remote pipe PTR WORD Receives pipe handle WORD Open mode for pipe Bit(s) Significance 02 Access mode 000 = inbound (client to server) 001 = outbound (server to client) 010 = bidirectional 36 Reserved (0) 7 Inheritance 0 = child process inherits pipe handle 1 = child does not inherit handle 813 Reserved (0) 14 Write-through flag 0 = writes to remote pipes can be buffered and deferred 1 = writes to remote pipes must be synchronous with the request 15 Reserved (0) WORD Pipe characteristics Bit(s) Significance 07 Maximum instance count (255 = unlimited) 8 Allowed read modes 0 = byte stream only 1 = byte stream or message stream (bit 10 must also = 1) 9 Reserved (0) 10 Write mode 0 = byte stream 1 = message stream 1114 Reserved (0) 15 Blocking mode 0 = wait if necessary to complete read or write operation 1 = return immediately with an error if read or write cannot be completed WORD Bytes to allocate for pipe's outbound data buffer (default = 1024) WORD Bytes to allocate for pipe's inbound data buffer (default = 1024) DWORD Default timeout interval in milliseconds for DosWaitNmPipe and DosConnectNmPipe (0 = 50 msec.) Notes: The maximum instance count is ignored after the first call to DosMakeNmPipe for a particular named pipe. After you create a named pipe, you can change its read mode and blocking mode with DosSetNmPHandState. Related functions: DosConnectNmPipe, DosMakePipe, DosOpen, DosQNmPHandState, DosSetNmPHandState, DosWaitNm. DosMakePipe [IOPL] Creates an anonymous (unnamed) pipe and returns read and write handles for the pipe. Pipe handles are inherited by child processes and can be used to communicate with those processes. When all the read and write handles for a pipe have been closed, the pipe is destroyed. PTR WORD Receives read handle for pipe PTR WORD Receives write handle for pipe WORD Maximum pipe size (advisory only; 0 = default of 512 bytes; maximum size = 65,504 bytes) Note: DosRead and DosWrite operate somewhat differently with pipes than they do with files or devices. If a pipe becomes full, DosWrite will block until enough data has been removed from the pipe so that the write can be completed; if a pipe is empty, DosRead will block until enough data is available. Related functions: DosClose, DosDupHandle, DosExecPgm, DosMakeNmPipe, DosRead, DosWrite. DosMemAvail [IOPL] Returns the size of the largest block of free memory. If segment motion is not disabled, the total free memory is returned. The returned value is advisory only and is subject to change at any time as a result of activity by other processes. Calls to this function do not cause segment motion, swapping, or discarding. PTR DWORD Receives size of largest free block DosMkDir [IOPL] [FAPI] Creates a directory. PTR ASCIIZ Pathname of new directory (can include drive) DWORD Reserved (0) Related functions: DosChDir, DosQCurDir, DosQSysInfo, DosRmDir. DosMonClose Releases a handle for a character device monitor; terminates all monitoring by the current process for the specified device. WORD Monitor handle from previous DosMonOpen Related functions: DosMonOpen, DosMonRead, DosMonReg, DosMonWrite. DosMonOpen Returns a handle for a character device monitor, or an error if the driver for the device does not support monitors. To initiate monitoring, follow the DosMonOpen call with a call to DosMonReg. PTR ASCIIZ Name of character device PTR WORD Receives monitor handle Note: In OS/2 versions 1.0 and 1.1, the base character device drivers that support monitors are KBD$, PRN, and MOUSE$. Related functions: DosMonClose, DosMonRead, DosMonReg, DosMonWrite. DosMonRead Reads a data packet from a character device monitor chain. The packet can be inspected, consumed, modified, or passed unchanged to DosMonWrite. PTR BUFFER Monitor input buffer specified in previous call to DosMonReg WORD Wait/no-wait flag 0 Wait until data is ready 1 Return with error if no data is ready PTR BUFFER Receives monitor data packet PTR WORD Contains length of buffer to receive packet; receives actual length of packet Note: The thread responsible for DosMonRead and DosMonWrite calls should be promoted to time-critical priority with DosSetPrty. It should not call any other API functions that might block and thereby degrade the performance of the device being monitored. Related functions: DosMonClose, DosMonOpen, DosMonReg, DosMonWrite. DosMonReg Registers input and output buffers for a character device monitor. WORD Monitor handle from previous call to DosMonOpen PTR BUFFER Monitor input buffer, with buffer length in the first word PTR BUFFER Monitor output buffer, with buffer length in the first word WORD Position requested in monitor chain 0 No preference 1 Head of chain 2 Tail of chain WORD Monitor index (device-specific; use session number for keyboard and mouse monitors) Notes: When multiple monitors request the head (or tail) position in the monitor chain, the first process becomes the actual head (or tail), the second process becomes second (or next-to-last), etc. A keyboard monitor that is started with the DETACH command (or the DosExecPgm option 4) can inspect the foreground screen group field of the global information segment to determine the session of its parent. The buffer size must be at least 20 bytes larger than the driver's monitor data packet. Although there is no general way to find the necessary buffer size at run time, 128 bytes is usually sufficient. Related functions: DosMonClose, DosMonOpen, DosMonRead, DosMonWrite. DosMonWrite Writes a data packet into a character device monitor chain. The process can obtain this packet using a previous DosMonRead call, or the process can synthesize it and insert it into the monitor chain. PTR BUFFER Monitor output buffer specified in previous call to DosMonReg PTR BUFFER Contains monitor data packet to be passed to the next monitor in the chain WORD Length in bytes of data packet Note: The thread responsible for DosMonRead and DosMonWrite calls should be promoted to time-critical priority with DosSetPrty. It should not call any other API functions that might block and thereby degrade the performance of the device being monitored. Related functions: DosMonClose, DosMonOpen, DosMonRead, DosMonReg. DosMove [IOPL] [FAPI] Renames a file and/or moves it from one directory to another. You can also use this function to rename directories other than the root directory. PTR ASCIIZ Old pathname (no wildcard characters) PTR ASCIIZ New pathname (no wildcard characters) DWORD Reserved (0) Notes: If a drive is included in the new pathname, it must match the drive specified in the old pathname or the current drive. An error is returned if any directory in the old or new pathnames does not exist. On FAT file systems in real mode, the filename and extension are truncated if necessary. In protected mode, if the filename or extension is too long, an error is returned. Related function: DosDelete. DosMuxSemWait [IOPL] Waits until at least one of a list of semaphores is cleared. Unlike the other semaphore wait functions, DosMuxSemWait is edge triggered; that is, the requesting thread will be unblocked even if the semaphore is set again before the thread can be dispatched. PTR WORD Receives list index (116) of semaphore that was cleared PTR BUFFER Contains a list of semaphores in the following format: Offset Length Description 00H 2 Number of semaphores (116) 02H 2 Reserved (0) 04H 4 Semaphore handle #1 08H 2 Reserved (0) 0AH 4 Semaphore handle #2 . . . . . . . . . DWORD Timeout interval in milliseconds (0 = return immediately with an error if none of the semaphores is clear; -1 = wait indefinitely) Related functions: DosSemClear, DosSemSet, DosSemSetWait, DosSemWait. DosNewSize [IOPL] [FAPI] Changes the size of a file. If the file is truncated, any data beyond the new end of file is lost; if the file is extended, the data beyond the previous end of file is undefined. WORD File handle DWORD New size of file in bytes Note: An error is returned if the file is read-only. Related functions: DosOpen, DosQFileInfo. DosOpen [IOPL] [FAPI] Creates, opens, or replaces a file, returning a handle which can be used for subsequent access to the file. The initial file pointer position is zero (the start of file). You can also open character devices and existing named pipes by their logical names as though they were files. PTR ASCIIZ Pathname of file (no wildcard characters) PTR WORD Receives file handle PTR WORD Receives DosOpen action Value Meaning 1 File existed and was opened 2 File did not exist and was created 3 File existed and was replaced DWORD Initial file allocation in bytes (if file is being created or replaced) WORD File attribute (if file is being created) Bit(s) Significance (if set) 0 Read-only 1 Hidden 2 System 34 Reserved (0) 5 Archive 615 Reserved (0) WORD Open flag Bits Significance 03 Action to take if file exists 0000 = fail 0001 = open 0010 = replace 47 Action to take if file does not exist 0000 = fail 0001 = create 815 Reserved (0) WORD Open mode Bit(s) Significance 02 Access mode 000 = read-only 001 = write-only 010 = read/write 3 Reserved 46 Sharing mode 001 = deny-read and deny-write (deny-all) 010 = deny-write 011 = deny-read 100 = deny-none 7 Inheritance 0 = handle is inherited by child processes 1 = handle is not inherited 812 Reserved (0) 13 Error mode 0 = hardware errors are reported through the system critical error handler 1 = hardware errors are returned to the process 14 Write-through flag 0 = writes can be buffered and deferred 1 = writes must be synchronous with the request 15 DASD open flag (see Notes) 0 = open file or character device 1 = open disk volume for direct access DWORD Reserved (0) Notes: When a file is created or replaced with an initial allocation, the contents of the file are undefined. The system attempts to allocate contiguous disk storage for the initial allocation. As an alternative to specifying a sharing mode of deny-write or deny-all, a program can use DosFileLocks to temporarily restrict access to specific regions of a file while it is updating them. The handle for the file is inherited by child processes unless bit 7 of the open mode is set, and any sharing and access restrictions are also inherited. However, the inheritance and error mode bits associated with the handle, as well as any locks placed on file regions with DosFileLocks, are not inherited. You can use DosSetFHandState to change some handle characteristics controlled by the open mode parameter after the file is opened. Similarly, you can change a file's attribute after it is created with DosSetFileMode. On FAT file systems in real mode, the filename and extension are truncated if necessary. In protected mode, if the filename or extension is too long, an error is returned. A protected mode DosOpen request with the DASD open flag set (1) allows an entire volume to be treated as a single file. The ASCIIZ pathname must consist of a drive letter followed by a colon, and the open flag must be 01H (that is, open if exists, fail if doesn't exist). A handle is returned which can be used with DosChgFilePtr, DosRead, and DosWrite to access any sector of the volume. In real mode, if the DASD open bit is set, the device is not actually opened. Instead, a handle is returned that you must use with DosDevIOCtl. When a Family application is running under MS-DOS, the sharing and access mode parameters are ignored unless file sharing (SHARE.EXE) is loaded, and the write-through and error mode bits must always be cleared (0). [1.1] When an existing named pipe is opened, bits 313 and bit 15 of the open mode must be zero, and bits 02 have the following meanings: 000 = inbound pipe (client to server) 001 = outbound pipe (server to client) 010 = bidirectional pipe [1.1] Named pipes are always opened by a client process in blocking and byte-stream mode. The mode can be changed with DosSetNmPHandState. Related functions: DosChgFilePtr, DosClose, DosDupHandle, DosExecPgm, DosMakeNmPipe, DosQFHandState, DosQFileInfo, DosQFileMode, DosQNmPHandState, DosSetFileInfo, DosSetFileMode, DosSetNmPHandState. DosOpenQueue Opens an existing queue and returns a handle, which can be used for subsequent access to the queue. A queue can be written by any process, but can be read only by the owner (creator) process. PTR WORD Receives PID of queue owner PTR WORD Receives queue handle PTR ASCIIZ Queue name, in the form \QUEUES\name.ext Note: The returned PID can be used with DosGiveSeg to make records written into the queue addressable by the queue owner. Related functions: DosCloseQueue, DosCreateQueue. DosOpenSem [IOPL] Opens an existing system (named) semaphore and returns a handle, which can be used for subsequent access to the semaphore. The semaphore must have been previously created with DosCreateSem. The semaphore persists until all the handles for it have been released with DosCloseSem. PTR DWORD Receives semaphore handle PTR ASCIIZ Semaphore name, in the form \SEM\name.ext Related functions: DosCloseSem, DosCreateSem. DosPeekNmPipe [1.1] Reads data from a named pipe without removing it from the pipe. WORD Pipe handle PTR BUFFER Receives data from pipe WORD Length of buffer to receive pipe data PTR WORD Receives actual length of data read from pipe PTR BUFFER Receives size information in the following format: Offset Length Description 00H 2 Bytes of data remaining in the pipe 02H 2 Bytes of data remaining in the inspected message PTR WORD Pipe status 1 Disconnected 2 Listening 3 Connected 4 Closing Note: DosPeekNmPipe never blocks, regardless of the current blocking mode of the pipe. Related functions: DosCallNmPipe, DosRead, DosTransactNmPipe. DosPeekQueue Retrieves the information associated with a queue record, without removing that record from the queue. Can be used by the queue owner to scan queue records so that they can be removed selectively (that is, not in their natural order) with DosReadQueue. WORD Queue handle PTR DWORD Receives queue record identification data Offset Length Description 00H 2 PID of queue writer 02H 2 Arbitrary data passed by queue writer PTR WORD Receives length of queue record PTR DWORD Receives address of queue record PTR WORD Contains record selection flag 0 = return first queue record 1 = return next queue record If nonzero, receives queue record identifier in the same location that can be used with a subsequent call to DosReadQueue WORD Wait/no-wait flag 0 = wait if no queue record is available 1 = return with error if queue is empty PTR BYTE Receives priority of queue record (015, not applicable if queue was not created with priority ordering) DWORD Handle of semaphore to be cleared when queue record becomes available, if wait/no-wait flag = 1; otherwise, a null pointer Note: If multiple threads are calling DosPeekQueue or DosReadQueue, all must use the same RAM or system semaphore. Related functions: DosMuxSemWait, DosPurgeQueue, DosQueryQueue, DosReadQueue, DosSemSet, DosSemWait, DosWriteQueue. DosPFSActivate Selects a code page and font for a specific process and printer. Intended for use by print spoolers and monitors; normal applications should use DosDevIOCtl for code page switching. WORD Handle for temporary spool file PTR DWORD Receives bytes written to spool file PTR ASCIIZ Logical name of printer device WORD Code page to make active WORD Font to make active WORD Process ID DWORD Reserved (0) Note: If code page and font are both zero, the hardware default code page and font are used. If only the font ID is zero, any font can be used. Related functions: DosPFSCloseUser, DosPFSInit, DosPFSQueryAct, DosPFSVerifyFont. DosPFSCloseUser Notifies the font switcher that a process has closed its spool file. The font switcher can then free any resources that were being used to track code page and font switching for the process. This function is intended for use by print spoolers and monitors and should not ordinarily be called by applications. PTR ASCIIZ Logical name of printer device WORD Process ID DWORD Reserved (0) Related functions: DosPFSActivate, DosPFSInit, DosPFSQueryAct, DosPFSVerifyFont. DosPFSInit Initializes code page and font switching for a printer. This function is intended for use by print spoolers and monitors and should not ordinarily be called by applications. PTR BUFFER Hardware font definition list in the following format: Offset Length Description 00H 2 Number of definitions in list 02H 2 Code page, definition #1 04H 2 Font ID, definition #1 06H 2 Code page, definition #2 08H 2 Font ID, definition #2 . . . . . . PTR ASCIIZ Pathname of font file PTR ASCIIZ Printer type ID PTR ASCIIZ Logical name of printer device WORD Maximum number of spool instances for which code page and font switching are to be tracked (advisory only) DWORD Reserved (0) Related functions: DosPFSActivate, DosPFSCloseUser, DosPFSQueryAct, DosPFSVerifyFont. DosPFSQueryAct Returns the active code page and font for a printer and process. This function is intended for use by print spoolers and monitors and should not ordinarily be called by applications. PTR ASCIIZ Logical name of printer device PTR WORD Receives code page PTR WORD Receives font ID WORD Process ID DWORD Reserved (0) Related functions: DosPFSActivate, DosPFSCloseUser, DosPFSInit, DosPFSVerifyFont. DosPFSVerifyFont Indicates whether a code page and font are available for the specified printer. This function is intended for use by print spoolers and monitors and should not ordinarily be called by applications. PTR ASCIIZ Logical name of printer device WORD Code page to verify WORD Font ID to verify (ID = 0 indicates that any font within the specified code page is acceptable) DWORD Reserved (0) Related functions: DosPFSActivate, DosPFSCloseUser, DosPFSInit, DosPFSQueryAct. DosPhysicalDisk [IOPL] Returns information about the number of partitionable fixed disks, or returns a handle that can be used to access a specific fixed disk with DosDevIOCtl calls. WORD Information request type 1 Obtain total number of partitionable disks 2 Obtain handle for use with category 9 DosDevIOCtl calls 3 Release handle PTR BUFFER Receives disk information (see Notes) WORD Length of buffer to receive disk information PTR BUFFER Contains user-supplied information (see Notes) WORD Length of user-supplied information Notes: The information returned by DosPhysicalDisk varies with the request type. It is formatted as follows: Type Information 1 Total number of partitionable disks in the system (2 bytes) 2 Handle for the specified partitionable disk (2 bytes) 3 None (pointer should be null) The user-supplied information passed to DosPhysicalDisk also varies with the request type. It is formatted as follows: Type Information 1 None (pointer and length for user-supplied information should be null) 2 ASCIIZ string that specifies the partitionable disk. The string is always three characters long: a 1-based ASCII disk number, followed by a colon (:), terminated by a null byte. 3 Handle previously obtained with request type 2 (2 bytes) Related functions: DosDevIOCtl, DosQFSInfo. DosPortAccess [IOPL] [FAPI] Requests or releases access to a continuous range of I/O ports. I/O ports can be read or written only within a ring 2 (IOPL) segment. WORD Reserved (0) WORD Request type 0 Obtain port access 1 Release port access WORD First port number WORD Last port number (may be same as first port number) Notes: CLI/STI privilege is also granted by this function; a separate call to DosCLIAccess is not necessary. In OS/2 versions 1.0 and 1.1, this function call has no effect. Related function: DosCallback, DosCLIAccess, DosR2StackRealloc. DosPTrace [IOPL] Provides an interface for tracing and debugging of another protected mode process. PTR BUFFER Used for communication between the debugger, DosPTrace function, and the target process Offset Length Description 00H 2 PID of process being debugged 02H 2 Thread ID of process being debugged 04H 2 Contains DosPTrace command code; receives DosPTrace result code 06H 2 Contains data for DosPTrace; receives DosPTrace error code or data 08H 2 Offset 0AH 2 Segment selector 0CH 2 Module handle 0EH 2 Register AX 10H 2 Register BX 12H 2 Register CX 14H 2 Register DX 16H 2 Register SI 18H 2 Register DI 1AH 2 Register BP 1CH 2 Register DS 1EH 2 Register ES 20H 2 Register IP 22H 2 Register CS 24H 2 CPU flags 26H 2 Register SP 28H 2 Register SS Notes: The DosPTrace commands are: Value Command 00H Invalid command 01H Return data from text (code) space 02H Return data from data space 03H Return register contents for specified thread 04H Write data into text (code) space 05H Write data into data space 06H Set register contents for specified thread 07H Run process (all threads) 08H Terminate process 09H Single-step process 0AH Initialize DosPTrace buffer 0BH Suspend specified thread 0CH Resume specified thread 0DH Convert segment number to selector 0EH Return floating-point registers 0FH Set floating-point registers 10H Get module name The DosPTrace codes returned at offset 04H of the structure are as follows: Value Significance 0 Success -1 Error -2 Signal -3 Single step -4 Breakpoint -5 Parity error -6 Process dying -7 GP fault -8 Library load -9 Numeric coprocessor error The DosPTrace values returned at offset 06H of the structure in the event of an error are as follows: Value Significance 1 Bad command 2 Process not found 3 Process not traceable Call DosExecPgm with option 3 to load a process for debugging. The DosPTrace buffer is initialized with command code 0AH. Breakpoints are set by writing Int 03H opcodes into the child process's instruction space with command code 04H. The child process can then be executed with command codes 07H or 09H. The parent process regains control when the child process terminates, when it causes an exception (such as GP fault or divide by zero), or when a breakpoint is reached. For DosPTrace commands 0EH and 0FH, offsets 08H0BH in the DosPTrace buffer contain a far pointer to a 94-byte area that contains or receives the floating-point registers. Related function: DosExecPgm. DosPurgeQueue Discards all records from a queue. Only the queue's owner (creator) process can use this function. WORD Queue handle Related functions: DosPeekQueue, DosQueryQueue, DosReadQueue, DosWriteQueue. DosPutMessage [FAPI] Writes a message to a file, pipe, or device. WORD Handle for file, pipe, or device WORD Length of message PTR BUFFER Contains message to be written Related functions: DosGetMessage, DosInsMessage, DosWrite. DosQAppType [IOPL] [1.1] Returns a code indicating the specified application's compatibility with the Presentation Manager and DOS compatibility mode. PTR ASCIIZ Pathname of application's executable file PTR WORD Receives application type Bit(s) Significance 02 000 = application type unknown 001 = cannot run in a window 010 = can run in a window (uses appropriate subset of Vio calls) 011 = Presentation Manager application 3 0 = not a bound (FAPI) application 1 = bound (FAPI) application 4 0 = application program 1 = dynlink library (bits 03 = 0) 5 0 = file is "new" (segmented) EXE format 1 = file is "old" EXE format (bits 04 = 0) 615 Reserved Notes: The application type is specified at link time with the NAME directive in the module definition file or afterwards with the MARKEXE utility. If the type is not specified, this function returns "unknown," and the application is run full-screen by the Presentation Manager. If the application pathname contains a : or \ delimiter, the function searches only the specified drive and directory for the file. Otherwise, it searches all directories in the current process's PATH. You can supply any extension; however, if the extension is absent, it defaults to EXE. The application type of the current process can be obtained from the local information segment. Related function: DosGetInfoSeg. DosQCurDir [IOPL] [FAPI] Returns the current directory for the specified drive. The current directory is maintained on a per-process basis and is inherited by child processes. WORD Drive number (0 = default, 1 = A, etc.) PTR BUFFER Receives ASCIIZ directory pathname PTR WORD Contains length of buffer to receive directory pathname; if buffer is too small, receives length of buffer needed (actual length of pathname is not returned in this variable) Notes: The returned string does not include the drive identifier or a leading backslash. [1.1] The maximum path length for the system can be obtained with DosQSysInfo. Related functions: DosChDir, DosQCurDisk, DosQSysInfo, DosSelectDisk. DosQCurDisk [IOPL] [FAPI] Returns a code for the current drive. The current drive is maintained on a per-process basis and is inherited by child processes. PTR WORD Receives current drive code (1 = A, 2 = B, etc.) PTR DWORD Receives logical drive bitmap (logical drives AZ correspond to bits 025; a bit is set if the logical drive exists) Related functions: DosQCurDir, DosSelectDisk. DosQFHandState [IOPL] [FAPI] Returns the sharing, access, inheritance, write-through, and error-handling characteristics associated with a file handle. WORD File handle PTR WORD Receives handle state Bit(s) Significance 02 Access mode 0000 = read-only 0001 = write-only 0010 = read/write 3 Reserved 46 Sharing mode 001 = deny-read/write (deny-all) 010 = deny-write 011 = deny-read 100 = deny-none 7 Inheritance 0 = handle is inherited by child processes 1 = handle is not inherited 812 Reserved 13 Error mode 0 = hardware errors are reported through the system critical error handler 1 = hardware errors are returned to the process 14 Write-through flag 0 = writes can be buffered and deferred 1 = writes must be synchronous with the request 15 DASD open flag (see DosOpen) 0 = handle represents file, pipe, or character device 1 = handle represents disk volume for direct access Note: [1.1] If the handle refers to a named pipe, only bits 7 and 14 are significant. Related functions: DosOpen, DosQFileInfo, DosQFileMode, DosQNmPHandState, DosSetFHandState. DosQFileInfo [IOPL] [FAPI] Given an active file handle, returns the date and time stamps, attributes, and size of the corresponding file. WORD File handle WORD Level of file information desired (always 1 for OS/2 versions 1.0 and 1.1) PTR BUFFER Receives file information, which has the following format for level 1: Offset Length Description 00H 2 File date of creation 02H 2 File time of creation 04H 2 File date of last access 06H 2 File time of last access 08H 2 File date of last write 0AH 2 File time of last write 0CH 4 File size 10H 4 File allocation 14H 2 File attribute WORD Size of buffer to receive file information Notes: For format of time, date, and attribute fields, see DosFindFirst. In FAT file systems, the date and time of creation and last access are always zero. In real mode, calls to this function will degrade the performance of DosOpen. Related functions: DosOpen, DosQFHandState, DosQFileMode, DosSetFileInfo. DosQFileMode [IOPL] [FAPI] Returns the attributes of a file or directory. PTR ASCIIZ Pathname of file PTR WORD Receives file attribute Bit(s) Significance (if set) 0 Read-only 1 Hidden 2 System 3 Reserved (0) 4 Directory 5 Archive 615 Reserved (0) DWORD Reserved (0) Related functions: DosOpen, DosQFHandState, DosQFileInfo, DosSetFileMode. DosQFSInfo [IOPL] [FAPI] Returns information about a logical volume, such as its name, available space, and allocation unit size. WORD Logical drive code (0 = current, 1 = A, 2 = B, etc.) WORD Level of file system information desired 1 = get logical drive characteristics 2 = get volume label PTR BUFFER Receives file system information (see Notes) WORD Size of buffer to receive file system information Notes: Level 1 file system information has the following format: Offset Length Description 00H 4 File system ID 04H 4 Sectors per allocation unit 08H 4 Total number of allocation units 0CH 4 Available allocation units 10H 2 Bytes per sector Level 2 file system information has the following format: Offset Length Description 00H 2 Volume label date of creation 02H 2 Volume label time of creation 04H 1 Length n of ASCIIZ volume label (not including null byte) 05H n+1 ASCIIZ volume label For date and time formats, see DosFindFirst. Related functions: DosDevIOCtl, DosPhysicalDisk, DosSetFSInfo. DosQHandType [IOPL] Returns a code indicating whether a handle is associated with a file, a pipe, or a device. WORD Handle for file, pipe, or device PTR WORD Receives handle information in a 2-byte structure Offset Length Description 00H 1 Handle type 0 = file 1 = character device 2 = pipe 01H 1 Handle bits Bit(s) Significance 06 Reserved (0) 7 0 = local device 1 = network device PTR WORD Receives attribute word from device driver header, if handle type = 1 (device) Note: Programs can determine whether their standard input or standard output handles are redirected by calling DosQHandType. If the handle type is 1 (device), the program should then test bit 0 (for standard input) or 1 (for standard output) of the device driver attribute word. When no redirection is occurring, the program can obtain better performance by using Kbd and Vio calls instead of DosRead and DosWrite. Related functions: DosMakePipe, DosOpen, DosQFHandState, DosQFileInfo, DosQFileMode. DosQNmPHandState [1.1] Returns the maximum number of pipe instances, allowed read modes, current write mode, and blocking mode information associated with a named pipe handle. WORD Pipe handle PTR WORD Receives handle state Bit(s) Significance 07 Instances allowed (255 = unlimited) 8 Allowed read modes 0 = byte stream only 1 = byte stream or message stream 9 Reserved 10 Write mode 0 = byte stream 1 = message stream 1113 Reserved (0) 14 Server/client flag 0 = client 1 = server 15 Blocking mode 0 = wait, if necessary, to complete read or write operation 1 = return immediately with an error if read or write cannot be completed Related functions: DosMakeNmPipe, DosQFHandState, DosQHandType, DosSetNmPHandState. DosQNmPipeInfo [1.1] Returns characteristics of the named pipe associated with a pipe handle. WORD Pipe handle WORD Information level (always 1 in OS/2 version 1.1) PTR BUFFER Receives pipe information Offset Length Description 00H 2 Length of pipe buffer for outbound data 02H 2 Length of pipe buffer for inbound data 04H 1 Maximum number of pipe instances (0 = unlimited) 05H 1 Current number of pipe instances 06H 1 Length of pipe name (n) 07H+ n+1 ASCIIZ pipe name PTR WORD Length of buffer to receive pipe information Related functions: DosMakeNmPipe, DosQNmPHandState. DosQNmPipeSemState [1.1] Returns information about the named pipes associated with a particular system semaphore. DWORD Semaphore handle PTR BUFFER Receives information about each pipe associated with the semaphore, as a series of 6-byte entries (see Note) WORD Length of buffer to receive information Note: The number of entries returned is the lesser of the number of pipes associated with the semaphore and the number of entries that can fit into the buffer. Each 6-byte entry takes the following form: Offset Length Description 00H 1 Pipe status 0 = end of information, remainder of buffer invalid 1 = data available in pipe 2 = free space available in pipe 3 = pipe is closing 01H 1 Waiting flag 1 = thread is waiting at other end of pipe 02H 2 Key associated with semaphore handle 04H 2 Bytes available to be read, or space available for writes (if pipe status = 1 or 2) Related functions: DosQNmPipeInfo, DosSetNmPipeSem. DosQSysInfo [1.1] Returns system information which does not change after the system is booted. WORD Information type 0 = get maximum path length PTR BUFFER Receives system information WORD Length of buffer to receive information Note: For information type 0, the receiving buffer need be only 2 bytes long. The drive identifier and leading backslash are included in the returned maximum length. Related functions: DosChDir, DosQCurDir. DosQueryQueue Returns the number of records in a queue. Any process that has opened the queue can use this function; it is not restricted to the queue owner. WORD Queue handle PTR WORD Receives number of records in queue Related functions: DosPeekQueue, DosPurgeQueue, DosReadQueue, DosWriteQueue. DosQVerify [IOPL] [FAPI] Returns the current state of the system flag that controls read-after-write verification of disk transfers. PTR WORD Receives verify flag 0 = verify mode off 1 = verify mode on Related function : DosSetVerify. DosR2StackRealloc [1.1] Resizes the current thread's ring 2 stack. WORD New ring 2 stack size (bytes) Note: The new stack size cannot be smaller than the current ring 2 stack. The default stack size for execution within an IOPL segment is 512 bytes. Related functions: DosCallback, DosCLIAccess, DosPortAccess. DosRead [IOPL] [FAPI] Reads data from a file, pipe, or device into a buffer. WORD Handle for file, pipe, or device PTR BUFFER Receives data WORD Number of bytes requested PTR WORD Receives actual number of bytes read Notes: When reading from the keyboard in ASCII mode, the DosRead operation is terminated by character codes 0DH (^M) or 1AH (^Z). If the CPU is in real mode, the user's entry is limited to the number of characters specified minus one; subsequent characters are ignored and cause a beep. If the CPU is in protected mode, the user can continue to enter characters until the keyboard driver's buffer is full, but the number of characters returned to the program is limited to the number requested. If the keyboard handle is in binary mode, DosRead always waits until the requested number of characters are available. When reading from a file, DosRead might return fewer bytes than requested if the end of file is reached; this is not an error condition. When reading from an anonymous pipe, DosRead blocks if the pipe is empty until data is available. [1.1] When you are reading from a named pipe, the result of the operation depends on the blocking and read modes of the pipe. If the pipe is in byte-stream mode, DosRead blocks if the pipe is empty unless the pipe is also in nonblocking mode. In message mode, if the buffer is larger than the message, the actual length of the message is returned. If the buffer is too small for the message, the buffer is filled and an error is returned; the remaining data must be obtained with another call to DosRead. Related functions: DosCallNmPipe, DosChgFilePtr, DosMakeNmPipe, DosOpen, DosReadAsync, DosTransactNmPipe, DosWrite DosWriteAsync. DosReadAsync [IOPL] Reads data from a file, pipe, or device into a buffer. The requesting thread continues to execute during the transfer, and a semaphore is cleared when the transfer is complete. WORD Handle for file, pipe, or device DWORD Handle of RAM semaphore to be cleared when transfer is complete PTR WORD Receives I/O error code (0 = no error) PTR BUFFER Receives data WORD Number of bytes requested PTR WORD Receives actual number of bytes read Notes: See DosRead for special considerations applying to the keyboard, pipes, and files. The file pointer is updated before the DosReadAsync function returns, even though the data transfer has not been completed. The usual order of requests is DosSemSet, then DosReadAsync, followed by other processing and then DosSemWait or DosMuxSemWait. Related functions: DosChgFilePtr, DosMakeNmPipe, DosMuxSemWait, DosOpen, DosRead, DosSemSet, DosSemWait, DosWrite, DosWriteAsync. DosReadQueue Reads and removes a record from a queue. Only the queue's owner (creator) process can call this function. WORD Queue handle PTR DWORD Receives queue record identification data Offset Length Description 00H 2 PID of queue writer 02H 2 Arbitrary data passed by queue writer PTR WORD Receives length of queue record PTR DWORD Receives address of queue record WORD Record identifier 0 = return first queue record <>0 = return specific queue record If nonzero, the identifier must have been obtained from a previous call to DosPeekQueue WORD Wait/no-wait flag 0 = wait if no queue record is available 1 = return with error if queue is empty PTR BYTE Receives priority of queue record (015, with 15 = highest priority; not applicable if queue was not created with priority ordering) DWORD Handle of semaphore to be cleared when queue record becomes available, if wait/no-wait flag = 1; otherwise, null pointer Note: If multiple threads are calling DosPeekQueue or DosReadQueue, all must use the same RAM or system semaphore. Related functions: DosMuxSemWait, DosPeekQueue, DosPurgeQueue, DosQueryQueue, DosSemSet, DosSemWait, DosWriteQueue. DosReallocHuge [IOPL] [FAPI] Changes the size of a huge memory block previously allocated with DosAllocHuge. If the block is discardable, DosReallocHuge performs an implicit DosLockSeg. The givable, gettable, or discardable attributes of the block are not affected. WORD Number of complete 64 KB segments WORD Number of bytes in partial last segment (0 = none) WORD Base selector obtained from DosAllocHuge Notes: In protected mode, the function fails if the new size exceeds the maximum number of segments specified in the original DosAllocHuge call. In real mode, the function rounds the requested size to the next paragraph (multiple of 16 bytes), and the maximum size specified in the original DosAllocHuge call is ignored. Related functions: DosAllocHuge, DosAllocSeg, DosFreeSeg, DosGetHugeShift, DosLockSeg, DosSizeSeg. DosReallocSeg [IOPL] [FAPI] Changes the size of a memory block previously allocated with DosAllocSeg or DosAllocShrSeg. If the block is discardable, DosReallocSeg performs an implicit DosLockSeg. The givable, gettable, or discardable attributes of the block are not affected. WORD New segment size in bytes (0 = 65,536) WORD Selector Notes: Shared segments can be increased but not decreased in size. In real mode, the function rounds the requested size to the next paragraph (multiple of 16 bytes). Related functions: DosAllocSeg, DosFreeSeg, DosLockSeg, DosR2StackRealloc, DosReallocHuge, DosSizeSeg. DosResumeThread [IOPL] Restarts a thread that was stopped with DosSuspendThread. WORD Thread ID Related functions: DosCreateThread, DosEnterCritSec, DosExitCritSec, DosSuspendThread. DosRmDir [IOPL] [FAPI] Removes a directory. Cannot be used on the root directory, the current directory, or a directory which contains files. PTR ASCIIZ Pathname of directory DWORD Reserved (0) Related functions: DosDelete, DosMkDir, DosQSysInfo. DosScanEnv [IOPL] Searches the current process's environment for a string in the form name=value; returns a pointer to value. PTR ASCIIZ Name of environment variable, must not include the = character PTR DWORD Receives address of ASCIIZ value string following the = character Related functions: DosGetEnv, DosSearchPath. DosSearchPath [IOPL] Searches one or more directories for a file, returning the fully qualified pathname if it is found. You can specify the directories explicitly (separated by semicolons) or supply the name of an environment variable. WORD Function control flags Bit(s) Significance 0 0 = search current directory only if it is explicitly named in path 1 = always search current directory before searching specified path 1 0 = explicit path is supplied 1 = name of environment variable is supplied; use the list of directories associated with that variable 215 Reserved PTR ASCIIZ Path to be searched (can contain multiple paths separated by semicolons) or name of an environment variable without a trailing = character PTR ASCIIZ Filename to be searched for (can contain wildcard characters) PTR BUFFER Receives fully qualified pathname (including an explicit drive and path from the root directory; wildcard characters in the filename or extension are not expanded) WORD Length of buffer to receive fully qualified pathname Related functions: DosFindFirst, DosGetEnv, DosScanEnv. DosSelectDisk [IOPL] [FAPI] Selects the current drive. The current drive is maintained on a per-process basis and is inherited by child processes. WORD Logical drive code (1 = A, 2 = B, etc.) Related functions: DosChDir, DosQCurDir, DosQCurDisk. DosSelectSession Switches the current session or a child session into the foreground. You cannot use this function to select the descendant of a child session, or a child session which is not "related." WORD Session ID (0 = current session) DWORD Reserved (0) Related functions: DosSetSession, DosStartSession, DosStopSession. DosSemClear [IOPL] Unconditionally clears (releases) a semaphore. DWORD Semaphore handle Note: Ordinarily, a system semaphore which was created with the exclusive option can be cleared only by the process that owns it. However, at interrupt time, any thread can clear any semaphore. Related functions: DosFSRamSemClear, DosMuxSemWait, DosSemRequest, DosSemSetWait, DosSemWait. DosSemRequest [IOPL] Obtains ownership of a semaphore. If the semaphore is already owned, the function waits until it is available. When used with system semaphores that were created with the exclusive option, DosSemRequest calls can be nested. DWORD Semaphore handle DWORD Timeout interval in milliseconds (0 = return immediately with an error if semaphore is owned; -1 = wait indefinitely) Related functions: DosFSRamSemRequest, DosSemClear. DosSemSet [IOPL] Unconditionally sets a semaphore. DWORD Semaphore handle Related functions: DosMuxSemWait, DosSemClear, DosSemSetWait, DosSemWait. DosSemSetWait [IOPL] Unconditionally sets a semaphore, then waits until it is cleared by another thread or process. This function is level-triggered; that is, if the semaphore is cleared and then set again before the waiting thread can be dispatched, the thread will continue to block. DWORD Semaphore handle DWORD Timeout interval in milliseconds (0 = return immediately with an error if semaphore is owned; -1 = wait indefinitely) Related functions: DosMuxSemWait, DosSemClear, DosSemSet, DosSemWait. DosSemWait [IOPL] Waits until a semaphore is cleared. This function is level-triggered; that is, if the semaphore is cleared and then set again before the waiting thread can be dispatched, the thread will continue to block. DWORD Semaphore handle DWORD Timeout interval in milliseconds (0 = return immediately with an error if semaphore is owned; -1 = wait indefinitely) Related functions: DosMuxSemWait, DosSemClear, DosSemSet, DosSemSetWait. DosSendSignal [IOPL] Sends a Ctrl-C or Ctrl-Break signal to the last process in a process subtree that has registered a handler for that signal. WORD PID of root process of a process subtree (must be a direct descendant of the current process) WORD Signal number 1 = SIGINTR (Ctrl-C) 4 = SIGBREAK (Ctrl-Break) Note: The specified process need not still be alive if any of its descendants are alive to receive the signal. Related functions: DosFlagProcess, DosHoldSignal, DosSetSigHandler. DosSetCp [IOPL] Selects a code page for the keyboard, display, and (if the spooler is loaded) the printer if it is opened subsequent to this function call. All processes in the current session use the new code page. WORD Code page identifier 000 Default (none) 437 USA 850 Multilingual 860 Portuguese 863 French-Canadian 865 Nordic WORD Reserved (0) Related functions: DosCaseMap, DosGetCollate, DosGetCp, DosGetCtryInfo, DosGetDBCSEv, DosSetProcCp, KbdSetCp, VioSetCp. DosSetDateTime [IOPL] [FAPI] Sets the system date, time, and time zone. PTR BUFFER Receives date and time information in the following format: Offset Length Description 00H 1 Hour (023) 01H 1 Minute (059) 02H 1 Second (059) 03H 1 Hundredths of second (099) 04H 1 Day (131) 05H 1 Month (112) 06H 2 Year (19802079) 08H 2 Time zone (minutes GMT, EST = 300, -1 = undefined) Related functions: DosGetDateTime, DosGetInfoSeg. DosSetFHandState [IOPL] [FAPI] Sets the inheritance, write-through, and error-handling characteristics of a file, pipe, or device handle. WORD Handle WORD Handle state Bit(s) Significance 02 Reserved (must be 0) 3 Reserved (use value returned by DosQFHandState) 46 Reserved (must be 0) 7 Inheritance 0 = handle inherited by child processes 1 = handle is not inherited 812 Reserved (use value returned by DosQFHandState) 13 Error mode 0 = hardware errors are reported through the system critical error handler 1 = hardware errors are returned to the process 14 Write-through flag 0 = writes may be buffered and deferred 1 = writes must be synchronous with the request 15 Reserved (must be 0) Notes: You cannot modify the sharing mode and access rights of a handle with this function because the new values might conflict with the values specified in previous calls to DosOpen by other processes. In real mode, the validity of the handle is not checked, and the write-through and error mode bits must always be cleared (0). If an FAPI program is running in an MS-DOS 2.x environment, you cannot set the inheritance bit. [1.1] If the handle refers to a named pipe, only bits 7 and 14 are significant. Related functions: DosNewSize, DosOpen, DosQFHandState, DosQNmPHandState, DosSetFileInfo, DosSetFileMode, DosSetNmPHandState. DosSetFileInfo [IOPL] [FAPI] Sets the time and date stamps in the directory entry for a file. The file must have been opened with write-only or read/write access rights. WORD File handle WORD File information level (always 1 in OS/2 versions 1.0 and 1.1) PTR BUFFER Contains file information, which has the following format for level 1: Offset Length Description 00H 2 File date of creation 02H 2 File time of creation 04H 2 File date of last access 06H 2 File time of last access 08H 2 File date of last write 0AH 2 File time of last write WORD Length of file information buffer Notes: Any date or time position that contains zero is ignored by this function. In FAT file systems, the creation and last access fields are not supported and should be zero. For format of time and date fields, see the notes for DosFindFirst. Related functions: DosNewSize, DosOpen, DosQFileInfo, DosSetFHandState, DosSetFileMode. DosSetFileMode [IOPL] [FAPI] Sets the attributes of a file or directory. PTR ASCIIZ Pathname of file WORD New attribute of file Bit(s) Significance (if set) 0 Read-only 1 Hidden 2 System 34 Reserved (0) 5 Archive 615 Reserved (0) DWORD Reserved (0) Note: If the pathname refers to a directory, only the read-only and hidden attribute bits can be set. Related functions: DosNewSize, DosOpen, DosQFileMode, DosSetFHandState, DosSetFileInfo. DosSetFSInfo [IOPL] [FAPI] Adds, modifies, or deletes a volume label. WORD Logical drive code (0 = default, 1 = A, 2 = B, etc.) WORD File system information level (always 2 in OS/2 versions 1.0 and 1.1) PTR BUFFER Contains file system information, which has the following format for level 2: Offset Length Description 00H 1 Length n of ASCIIZ volume label (excluding null byte) 01H n+1 ASCIIZ volume label WORD Length of buffer containing file system information Related functions: DosDevIOCtl, DosPhysicalDisk, DosQFSInfo. DosSetMaxFH [IOPL] Sets the maximum number of active handles for files, pipes, and devices for the current process. Note that some handles are expended for the standard devices, dynlink libraries, and files, pipes, and device handles inherited from the parent process, even though the current process has explicitly opened none of these. WORD Number of file handles (default = 20, maximum = 255) Related function: DosOpen. DosSetNmPHandState [1.1] Modifies the current read mode and blocking mode for a named pipe. WORD Pipe handle WORD Handle state Bit(s) Significance 07 Reserved (0) 8 Allowed read modes 0 = byte stream only 1 = byte stream or message stream 914 Reserved (0) 15 Blocking mode 0 = wait, if necessary, to complete read or write operation 1 = return immediately with an error if read or write cannot be completed Note: Bit 8 of the handle state cannot be set with this function unless the pipe was created with the "write message mode" bit set. Related functions: DosMakeNmPipe, DosQFHandState, DosQNmPHandState, DosSetFHandState. DosSetNmPipeSem [1.1] Associates a system semaphore with a named pipe. WORD Pipe handle DWORD Semaphore handle WORD Semaphore key Notes: The semaphore is cleared whenever the pipe has data available for reading or space available for writing. The key is returned by subsequent calls to DosQNmPipeSemState. It allows the process to determine which pipe is ready for I/O when several pipes are associated with the same semaphore. If a semaphore is already associated with the specified named pipe, it is replaced with the new semaphore. Related functions: DosCreateSem, DosMuxSemWait, DosOpenSem, DosQNmPipeSemState, DosSemWait. DosSetProcCp Selects the process code page, code-page-related country-dependent information, and (if the spooler is loaded) the code page used by the printer for subsequent open operations. The keyboard and display code pages for the current process, and the code pages used by other processes, are not affected. WORD Code page identifier 000 Default (none) 437 USA 850 Multilingual 860 Portuguese 863 French-Canadian 865 Nordic WORD Reserved (0) Related functions: DosCaseMap, DosGetCollate, DosGetCp, DosGetCtryInfo, DosGetDBCSEv, DosSetCp, KbdSetCp, VioSetCp. DosSetPrty [IOPL] Sets the priority of a single thread in the current process or of all threads within another process or an entire process subtree. WORD Scope of function 0 All threads of another process 1 All threads of another process and all of its descendants 2 Single thread within the current process WORD Priority class 0 Current class unchanged 1 Idle-time 2 Regular 3 Time-critical WORD Priority level (see Notes) WORD Process ID (scope = 0 or 1) or thread ID (scope = 2) Notes: If the scope is 1, the specified process must be either the current process or a nondetached child process. The specified process need not still be alive for its descendants to be affected. When DosSetPrty is used on another process, only threads with default priority are affected; threads whose priority has been altered after the process was started are not affected. If the priority class is 1, 2, or 3, the priority level is the initial priority level (031) within the class. If the priority class is 0 (current class unchanged), the level parameter is an increment (-31 through 31) to be applied to the current level; the resulting level is clamped to the range 031. Related functions: DosCreateThread, DosGetPrty. DosSetSession Determines whether a child session can be selected by the user, and/or whether the child session is made visible when the parent session is selected. WORD Session ID from previous DosStartSession PTR BUFFER Session status data Offset Length Description 00H 2 Length (always 6 in OS/2 versions 1.0 and 1.1) 02H 2 Selectability indicator 0 = current setting unchanged 1 = session is selectable 2 = session is not selectable 04H 2 Binding indicator 0 = current setting unchanged 1 = child session is bonded to parent; when either the parent or child session is selected, the child session is displayed 2 = child session is not bonded to parent; parent session and child sessions can be selected and displayed independently Note: DosSetSession can be used only to control a "related" session that was started by the current process. Related functions: DosSelectSession, DosStartSession, DosStopSession. DosSetSigHandler [IOPL] [FAPI] Registers a handler for a signal, restores the default handler, causes the system to ignore the signal or return an error to the signaling process, or resets a signal during its handling so that another signal of the same type can be processed. DWORD Address of new signal handler PTR DWORD Receives address of previous signal handler (can use null pointer) PTR WORD Receives previous signal handler action (can use null pointer) WORD New signal handler action 0 Install default system action 1 Ignore the signal 2 Register new handler for signal 3 Return an error to the signaling process 4 Reset the signal without affecting the disposition of the signal (used only by signal handler) WORD Signal number of interest 1 Ctrl-C (SIGINTR) 2 Broken pipe (SIGBROKENPIPE) 3 Program terminated (SIGTERM) 4 Ctrl-Break (SIGBREAK) 5 Event flag A 6 Event flag B 7 Event flag C Notes: A signal handler is given control under the primary thread of a process with the stack set up as follows: SS:SP 32-bit return address for handler SS:SP+4 Signal number SS:SP+6 Private data from signaling process, if the signal number is 5, 6, or 7 During its signal processing, the signal handler must reset the signal by calling DosSetSigHandler with action 4. The handler can retain control, resetting the stack and transferring to some other point in the application, or it can exit with a RET 4 instruction. In real mode, the Ctrl-C (SIGINTR) and Ctrl-Break (SIGBREAK) signals are treated as the same signal, and a handler can be installed only for SIGINTR. In OS/2 version 1.0, if thread 1 of a process terminates, the signal handlers revert to the default action. In version 1.1, if thread 1 terminates, the process is terminated. Related functions: DosFlagProcess, DosHoldSignal, DosSendSignal. DosSetVec [IOPL] [FAPI] Registers a handler for one of the following hardware exceptions: divide by zero, divide overflow, array bounds exceeded, invalid opcode, numeric coprocessor error, or numeric coprocessor not present. You cannot register handlers for general protection and stack faults; these exceptions are always handled by the operating system and terminate the process. WORD Exception type 0 Divide by zero 4 Overflow flag set and INTO executed 5 Bounds exceeded 6 Invalid opcode 7 Numeric coprocessor not available 16 Numeric coprocessor error DWORD Address of exception handler PTR DWORD Receives address of previous exception handler (cannot use null pointer) Notes: The handler is entered with flags and a far return address on the stack and with interrupts enabled. The handler can keep control (resetting its stack frame) and transfer to another point in the program, or it can return from the exception with an IRET. In real mode, you cannot register a handler for exception type 7 (numeric coprocessor not available) because it is not generated by 8086/88 machines. Related functions: DosError, DosSetSigHandler. DosSetVerify [IOPL] [FAPI] Turns on or turns off the system flag for read-after-write verification of disk transfers. WORD New value of verify flag 0 Verify is disabled 1 Verify is enabled Related function: DosQVerify. DosSizeSeg [IOPL] [1.1] Returns the size of a memory segment in bytes. WORD Selector PTR DWORD Receives segment size Notes: You must use the base selector returned by DosAllocHuge when you obtain the size of a huge segment. If a segment has been discarded, DosSizeSeg still returns the original allocation size for the segment. Related functions: DosAllocHuge, DosAllocSeg, DosAllocShrSeg, DosFreeSeg, DosReallocHuge, DosReallocSeg. DosSleep [IOPL] [FAPI] Suspends execution for at least the requested interval. The actual interval can vary from the requested interval by one or more clock ticks depending on other activity in the system. DWORD Interval in milliseconds, rounded up by the system to the next multiple of a clock tick interval (31 msec. in OS/2 versions 1.0 and 1.1) Note: If the specified interval is zero, the thread simply yields the remainder of its current timeslice. Related functions: DosGetInfoSeg, DosTimerAsync, DosTimerStart. DosStartSession Creates a new session (screen group) and starts a process within that session. In OS/2 version 1.1, this function can also be used to start a Vio or Presentation Manager application in a window. PTR BUFFER Contains session control data Offset Length Description 00H 2 Structure length (always 24 bytes in OS/2 version 1.0; 24, 30, or 50 bytes in version 1.1) 02H 2 Related session flag 0 = new session is independent 1 = new session is related (child) 04H 2 Foreground/background flag 0 = new session is in foreground 1 = new session is in background 06H 2 Tracing option 0 = process is not traceable 1 = process is traceable 08H 4 Address of ASCIIZ program title 0CH 4 Address of ASCIIZ program pathname 10H 4 Address of ASCIIZ command tail 14H 4 Address of ASCIIZ queue name or null pointer --------------(End of 24-byte structure)-------------- 18H 4 Address of environment block or null pointer 1CH 2 Inheritance option 0 = inherit Task Manager's default environment, current disk, and current directory 1 = inherit parent's environment, handles, current disk, and current directory --------------(End of 30-byte structure)-------------- 1EH 2 Program type 0 = use type from EXE file or Task Manager default 1 = full screen 2 = Vio application in window 3 = Presentation Manager application 20H 4 Address of ASCIIZ icon filename or null pointer 24H 4 Program handle for entry in install file (can be 0) 28H 2 Program control for initial state 0 = use installed state or Task Manager default 1 = use size and position specified 2 = minimized window 3 = maximized window 4 = invisible window 2AH 2 Initial x coordinate, lower left corner of window 2CH 2 Initial y coordinate, lower left corner of window 2EH 2 Initial window width 30H 2 Initial window height --------------(End of 50-byte structure)-------------- PTR WORD Receives session ID PTR WORD Receives PID Notes: For independent sessions, the queue name parameter is ignored, and a session ID is not returned. You cannot specify an independent session as the target of a call to DosSelectSession, DosSetSession, or DosStopSession. A session ID is returned for related child sessions; it can be used with DosSelectSession, DosSetSession, and DosStopSession. If you specify a queue name, the Task Manager writes a record in the following form into the queue when the child session terminates: Offset Length Description 00H 2 Session ID of terminating child session 02H 2 Exit code of process in child session The queue can be read only by the process which requested the DosStartSession. After the queue message is received and processed, release its selector with DosFreeSeg. A child session is not equivalent to a child process started with DosExecPgm; the returned PID cannot be used with DosCwait or DosSetPrty. If the child session is in the foreground when its last process terminates, the parent session is brought to the foreground. [1.0] The environment of the initial process in the child session is empty; that is, it consists of a pair of zero bytes. In OS/2 version 1.1 the environment of the initial process is empty except for a copy of the parent's PATH. [1.1] If the 24-byte session control data structure is used, or if the 30- or 50-byte session control data structure is used and the inheritance option and environment block pointer are both zero, the new process's environment is determined by the PATH and SET directives in the CONFIG.SYS file, and the parent's handles, current disk, and current directory are not inherited. [1.1] If the 30- or 50-byte session control data structure is used, the inheritance option is 1, and the environment block pointer is zero, then the new process's environment is a copy of the parent's. [1.1] The initial window size and location parameters are expressed in graphics coordinates, and the origin, or (x,y) = (0,0), is the lower left corner of the screen. These parameters are ignored if the program control parameter is not equal to 1. Related functions: DosCreateQueue, DosExecPgm, DosReadQueue, DosSelectSession, DosSetSession, DosStopSession. DosStopSession Terminates a related child session that was created with DosStartSession. If the child session was in the foreground, the parent session is brought into the foreground. WORD Target session option 0 Terminate only the specified session and its descendants 1 Terminate all child sessions and their descendants WORD Session ID (ignored if target session option = 1) DWORD Reserved (0) Related functions: DosSelectSession, DosSetSession, DosStartSession. DosSubAlloc [IOPL] [FAPI] Allocates a memory block from a heap that was previously initialized by DosSubSet. WORD Selector of segment containing heap PTR WORD Receives offset of allocated block WORD Size in bytes of requested block (rounded if necessary to next multiple of 4; maximum size is the size of the segment minus 8 bytes) Related functions: DosSubFree, DosSubSet. DosSubFree [IOPL] [FAPI] Releases a memory block that was previously obtained with DosSubAlloc. WORD Selector of segment containing heap WORD Offset of block to free WORD Size in bytes of block to free (rounded if necessary to next multiple of 4) Related functions: DosSubAlloc, DosSubSet. DosSubSet [IOPL] [FAPI] Initializes or resizes a heap. WORD Selector for segment that will contain heap WORD Action 0 Increase size of existing heap 1 Initialize heap WORD Total size in bytes of heap (minimum = 12 bytes; 0 = 65,536 bytes; rounded if necessary to next multiple of 4 bytes) Related functions: DosAllocSeg, DosReallocSeg, DosSubAlloc, DosSubFree. DosSuspendThread [IOPL] Suspends execution of another thread within the same process. WORD Thread ID Related functions: DosCreateThread, DosEnterCritSec, DosExitCritSec, DosResumeThread. DosTimerAsync [IOPL] Starts an asynchronous one-shot timer that clears a system semaphore after the specified interval. The function returns a handle that can be used to disable the timer with DosTimerStop. Set the semaphore before calling this function. DWORD Timer interval in milliseconds DWORD Handle for system semaphore PTR WORD Receives timer handle Note: OS/2 rounds the specified interval to the next multiple of the system's timer tick interval, which is 31 msec. in OS/2 versions 1.0 and 1.1. Related functions: DosCreateSem, DosSemSet, DosSemWait, DosSleep, DosTimerStart, DosTimerStop. DosTimerStart [IOPL] Starts an asynchronous timer that periodically clears a system semaphore. The function returns a handle that can be used to disable the timer with DosTimerStop. Set the semaphore before calling this function, and set it again immediately each time it is cleared by the timer. DWORD Timer interval in milliseconds DWORD Handle for system semaphore PTR WORD Receives timer handle Note: OS/2 rounds the specified interval up to the next multiple of the system's timer tick interval, which is 31 msec. in OS/2 versions 1.0 and 1.1. Related functions: DosCreateSem, DosSemSet, DosSemWait, DosSleep, DosTimerAsync, DosTimerStop. DosTimerStop [IOPL] Stops a timer that was previously created with DosTimerStart or DosTimerAsync. WORD Timer handle Related functions: DosTimerAsync, DosTimerStart. DosTransactNmPipe [1.1] Writes a message to a named pipe, and then reads a message from the same pipe. WORD Pipe handle PTR BUFFER Contains data to be written to pipe WORD Length of data to be written to pipe PTR BUFFER Receives data from pipe WORD Length of buffer to receive data from pipe PTR WORD Receives actual length of data read from pipe Notes: This function fails if the pipe is not in message mode or contains any unread data. DosTransactNmPipe is not affected by a pipe's blocking mode; it does not return until a message is available. If the buffer is too small for the message, the buffer is filled and an error is returned. To obtain the remaining data, you must make another call to DosTransactNmPipe. Related functions: DosCallNmPipe, DosMakeNmPipe, DosRead, DosWrite. DosUnlockSeg [IOPL] Notifies the operating system that it can discard a segment in low-memory situations; reverses the effect of a previous call to DosLockSeg. DosLockSeg and DosUnlockSeg calls may be nested. The segment must have been allocated by DosAllocSeg or DosAllocHuge with the "discardable" option. WORD Selector Related functions: DosAllocHuge, DosAllocSeg, DosLockSeg, DosReallocHuge, DosReallocSeg. DosWaitNmPipe [1.1] Waits for an available instance of a named pipe. Call this function if DosOpen returns an error indicating that the pipe is busy (that is, all allowed instances of the pipe are in use). When DosWaitNmPipe succeeds, issue another DosOpen to obtain a named pipe handle. PTR ASCIIZ Pipe name, in the form \PIPE\name.ext for a local pipe or \\machine\PIPE\name.ext for a remote pipe DWORD Timeout interval in milliseconds (0 = use pipe default timeout; -1 = wait indefinitely) Note: This function is used only by client processes. Related function: DosOpen. DosWrite [IOPL] [FAPI] Writes data from a buffer to a file, pipe, or device. WORD Handle for file, pipe, or device PTR BUFFER Contains data to be written WORD Requested number of bytes to be written PTR WORD Receives actual number of bytes written Notes: When writing to a file, DosWrite might transfer fewer bytes than requested if the disk containing the file becomes full; this is not an error condition. When writing to an anonymous pipe that becomes full, DosWrite blocks until enough room is available in the pipe to hold the requested number of bytes. [1.1] When writing to a named pipe in message mode or to a pipe in both byte-stream and blocking modes, DosWrite blocks until all the data can be written. When writing to a named pipe in byte-stream and nonblocking modes, DosWrite returns immediately, and the number of bytes written may be less than the number requested. Related functions: DosCallNmPipe, DosChgFilePtr, DosMakeNmPipe, DosRead, DosReadAsync, DosTransactNmPipe, DosWriteAsync. DosWriteAsync [IOPL] Writes data from a buffer to a file, pipe, or device. The requesting thread continues to execute during the transfer, and a RAM semaphore is cleared when the transfer is complete. WORD Handle for file, pipe, or device DWORD Handle of RAM semaphore to be cleared when transfer is complete PTR WORD Receives I/O error code (0 = no error) PTR BUFFER Contains data to be written WORD Requested number of bytes to be written PTR WORD Receives actual number of bytes written Notes: See DosWrite for special considerations applying to pipes and files. After you call DosWriteAsync, the data to be written should not be modified until the semaphore has been cleared. The file pointer is updated before the DosWriteAsync function returns, even though the data transfer has not been completed. The usual order of requests is DosSemSet, then DosWriteAsync, followed by other processing and then DosSemWait or DosMuxSemWait. Related functions: DosChgFilePtr, DosMakeNmPipe, DosMuxSemWait, DosRead, DosReadAsync, DosSemSet, DosSemWait, DosWrite. DosWriteQueue Adds a record to a queue. Any process can write to a queue, but only the queue owner (creator) process can inspect or remove records in the queue. WORD Queue handle WORD Request identification data (passed to queue reader, but ignored by OS/2) WORD Length of queue record DWORD Address of queue record (valid selector must be obtained for the queue reader with DosGiveSeg, or the selector must be allocated as a "gettable" selector) WORD Queue record priority (see Note) Note: The queue record priority can be in the range 0 through 15 and is relevant only if the queue was created with priority ordering. Priority 15 records are added to the head of the queue; priority 0 records are added to the tail. Records with the same priority are added to the queue in FIFO order. Related functions: DosPeekQueue, DosPurgeQueue, DosQueryQueue, DosReadQueue. KbdCharIn [FAPI] Returns a character and scan code from the logical keyboard. The character is not echoed to the display. PTR BUFFER Receives character data Offset Length Description 00H 1 ASCII character 01H 1 Scan code 02H 1 Status Bit(s) Significance (if set) 0 Shift status returned without character (valid only in binary mode when shift reporting is turned on) 14 Reserved (0) 5 On-the-spot conversion requested 6 Character ready 7 Interim character 03H 1 Reserved (0) 04H 2 Keyboard shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 SysReq key down 06H 4 Time stamp (msec. since system was turned on or restarted) WORD Wait/no-wait flag 0 Wait for a character if none is ready 1 Do not wait for a character; return an error if no character is ready WORD Keyboard handle (default = 0) Notes: Extended characters (such as function keys, cursor keys, and Alt-key combinations) are indicated by a 00H or an E0H in the ASCII character field. The key's identity is determined from the scan code. If the keyboard is in ASCII ("cooked") mode, some control keys (such as ^S) receive special handling. If binary ("raw") mode is turned on with KbdSetStatus, all control keys are passed through to the application; if shift reporting is also turned on, shift keys alone are also returned as keystrokes. To read double-byte character set (DBCS) codes, you must make two calls to KbdCharIn. The first character of a DBCS code is indicated by bit 7 = 1 in the status byte. In real mode, the keyboard handle is ignored. Related functions: DosRead, KbdPeek, KbdSetStatus, KbdStringIn. KbdClose Destroys a logical keyboard that was created with KbdOpen. If the logical keyboard has the input focus, the focus is freed. Any characters waiting in the logical keyboard's input buffer are discarded. WORD Keyboard handle Note: The default logical keyboard (0) cannot be closed. Related functions: KbdFlushBuffer, KbdFreeFocus, KbdOpen. KbdDeRegister [XPM] Restores the default keyboard input routines for the current screen group; deregisters any alternative keyboard API routines that were installed with KbdRegister. No parameters Related functions: KbdRegister, MouDeRegister, VioDeRegister. KbdFlushBuffer [FAPI] Clears the keyboard input buffer and discards any waiting characters. The buffer is flushed only if the indicated keyboard has the input focus or is the default keyboard. WORD Keyboard handle (default = 0) Note: In real mode, the keyboard handle is ignored. Related functions: KbdCharIn, KbdGetFocus, KbdStringIn. KbdFreeFocus Removes the bond between the physical keyboard and the specified logical keyboard. WORD Keyboard handle Note: The default keyboard handle (0) should not be used with this function. Related functions: KbdClose, KbdGetFocus, KbdOpen. KbdGetCp Returns an identifier for the code page which is currently used by the keyboard subsystem to translate scan codes into ASCII characters. DWORD Reserved (0) PTR WORD Receives code page ID WORD Keyboard handle (default = 0) Related functions: DosGetCp, DosSetCp, KbdSetCp, VioGetCp, VioSetCp. KbdGetFocus Binds the specified logical keyboard to the physical keyboard for subsequent input calls. Only one process and one logical keyboard can own the keyboard input focus at any given time; other processes calling KbdGetFocus will be blocked or receive an error status until the focus is available. WORD Wait/no-wait flag 0 Wait until the keyboard is available 1 Do not wait for the focus; return with an error if the keyboard is not available WORD Keyboard handle Note: The default keyboard handle (0) should not be used with this function. Related functions: KbdFreeFocus, KbdOpen. KbdGetStatus [FAPI] Obtains the current state of the keyboard, including the input mode, echo state, shift state, interim character flags, and the turnaround (logical end-of-line) character. PTR BUFFER Receives keyboard status in the following format: Offset Length Description 00H 2 Size (10 in OS/2 versions 1.0 and 1.1) 02H 2 Status Bit(s) Significance (if set) 0 Echo is on 1 Echo is off 2 Binary ("raw") mode is on 3 ASCII ("cooked") mode is on 46 Reserved 7 Turnaround character is 2 bytes 8 Shift reporting is on 915 Reserved Offset Length Description 04H 2 Turnaround character (if bit 7 of the status byte is 0, the upper byte of this field is undefined) 06H 2 Interim character flags Bit(s) Significance (if set) 04 Reserved 5 On-the-spot conversion requested 6 Reserved 7 Interim character 815 Reserved 08H 2 Keyboard shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 SysReq key down WORD Keyboard handle (default = 0) Notes: When the keyboard is in binary mode, and shift reporting is on, shift keys alone are also reported as keystrokes. In real mode, the keyboard handle is ignored, and the function does not support interim characters or the turnaround character. Related function: KbdSetStatus. KbdOpen Creates a new logical keyboard and returns a handle, which can be used to bind the logical keyboard to the physical keyboard with KbdGetFocus. The new keyboard is initialized to use the system default code page. PTR WORD Receives keyboard handle Related functions: DosGetCp, DosSetCp, KbdClose, KbdFreeFocus, KbdGetCp, KbdGetFocus, KbdSetCp. KbdPeek [FAPI] Returns the next keyboard character and its scan code, if a character is waiting, without removing it from the keyboard input buffer. This allows a program to "look ahead" by one character. PTR BUFFER Receives character data Offset Length Description 00H 1 ASCII character code 01H 1 Scan code 02H 1 Status Bit(s) Significance (if set) 0 Shift status returned without character 14 Reserved (0) 5 On-the-spot conversion requested 6 Character ready 7 Interim character 03H 1 Reserved (0) 04H 2 Shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 SysReq key down 06H 4 Time stamp (msec. since the system was turned on or restarted) WORD Keyboard handle (default = 0) Notes: If bit 6 of the status byte (offset 2 in the buffer) is 1, a character is ready and the remainder of the buffer is valid. If the same bit is 0, no character is waiting and the remaining data is undefined. Extended characters (such as cursor keys or Alt-key combinations) are indicated by a 00H or an E0H in the ASCII character field. The key is identified by examination of the scan code. If the keyboard is in ASCII ("cooked") mode, some control keys (such as ^S) receive special handling. If binary ("raw") mode is turned on with KbdSetStatus, all control keys are passed through to the application; if shift reporting is also turned on, shift keys alone are also returned as keystrokes. To read double-byte character set (DBCS) codes, make two calls to KbdPeek. The first character of a DBCS code is indicated by bit 7 = 1 in the status byte. In real mode, the function does not support interim characters and does not return the time stamp in the data structure. The status field can take the values 00H or 40H. The keyboard handle is ignored. Related functions: KbdCharIn, KbdSetStatus, KbdStringIn. KbdRegister [XPM] Registers a replacement routine to service one or more keyboard API functions for the current screen group. Functions which are not registered continue to be serviced by the Base Keyboard Subsystem (BKSCALLS.DLL). PTR ASCIIZ Pathname of dynlink library PTR ASCIIZ Name of library entry point DWORD Identifies the keyboard function(s) being registered Bit(s) Function (if set) 0 KbdCharIn 1 KbdPeek 2 KbdFlushBuffer 3 KbdGetStatus 4 KbdSetStatus 5 KbdStringIn 6 KbdOpen 7 KbdClose 8 KbdGetFocus 9 KbdFreeFocus 10 KbdGetCp 11 KbdSetCp 12 KbdXlate 13 KbdSetCustXt 1431 Reserved (0) Notes: A registered replacement for a keyboard subsystem receives control with the following items on the stack: 32-bit KBDCALLS.DLL return address (top of stack) 16-bit application program's DS register 16-bit KBDCALLS.DLL internal return address 16bit function code 0 KbdCharIn 1 KbdPeek 2 KbdFlushBuffer 3 KbdGetStatus 4 KbdSetStatus 5 KbdStringIn 6 KbdOpen 7 KbdClose 8 KbdGetFocus 9 KbdFreeFocus 10 KbdGetCp 11 KbdSetCp 12 KbdXlate 13 KbdSetCustXt 32-bit application return address Function-specific parameters placed on stack by application program The replacement routine must exit by a far return with the stack unchanged. The contents of AX are interpreted as follows: AX <> -1 Kbd function performed, AX contains zero or error code; return to application AX = -1 Replacement subsystem did nothing; invoke the appropriate base subsystem routine and return its status to the application Replacement keyboard API routines can be registered by only one process per screen group at a time. KbdRegister calls by other processes fail until the default keyboard routines are restored with KbdDeRegister. Related functions: KbdDeRegister, KbdFlushBuffer, MouRegister, VioRegister. KbdSetCp Selects the code page that is used to translate keyboard scan codes into ASCII characters for the current screen group. WORD Reserved (0) WORD Code page ID 0000 Default code page 0437 USA 0850 Multilingual 0860 Portuguese 0863 French-Canadian 0865 Nordic WORD Keyboard handle (default = 0) Related functions: DosSetCp, KbdGetCp, KbdSetCustXt, VioSetCp. KbdSetCustXt Installs a custom code page for the specified logical keyboard. The code page is used to translate scan codes to ASCII characters. PTR BUFFER Contains translate table WORD Keyboard handle (default = 0) Note: The system uses the custom code page in its original location within the requesting process's memory space. The process must maintain the code page in place until another code page is selected with KbdSetCp, DosSetCp, or KbdSetCustXt. Related functions: DosSetCp, KbdSetCp, KbdXlate, VioSetCp. KbdSetStatus [FAPI] Sets the state of certain keyboard toggles and flags that affect the behavior of the keyboard input operations KbdCharIn, KbdPeek, and KbdStringIn. PTR BUFFER Contains keyboard status in the following form: Offset Length Description 00H 2 Size (10 in OS/2 versions 1.0 and 1.1) 02H 2 Mask for changes to keyboard state Bit(s) Significance (if set) 0 Echo is on 1 Echo is off 2 Binary ("raw") mode is on 3 ASCII ("cooked") mode is on 4 Modify shift state 5 Modify interim character flag 6 Modify turnaround character 7 Turnaround character is two bytes (meaningful only if bit 6 = 1) 8 Turn on shift reporting 915 Reserved 04H 2 Turnaround character (if bit 7 of the mask word is 0, only the lower byte of this field is significant) 06H 2 Interim character flags Bit(s) Significance (if set) 04 Reserved (0) 5 On-the-spot conversion requested 6 Reserved (0) 7 Interim character 815 Reserved (0) 08H 2 Keyboard shift state Bit(s) Significance (if set) 03 Reserved 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 815 Reserved WORD Keyboard handle (default = 0) Notes: In normal use, KbdGetStatus should be called first to fill in the structure with valid information. The appropriate fields can then be changed and written back to the system with KbdSetStatus. If bits 0 and 1 of the keyboard mask are both cleared (0), the echo state of the keyboard is not altered. If bits 2 and 3 are both cleared (0), the binary/ASCII state of the keyboard is not altered. If bits 0 and 1 are both set or if bits 2 and 3 are both set, the KbdSetStatus call returns an error. In real mode, interim characters and the turnaround character are not supported. Binary mode with echo on is not supported. The keyboard handle is ignored. Related functions: KbdCharIn, KbdGetStatus, KbdPeek, KbdStringIn. KbdStringIn [FAPI] Reads a string of characters from the keyboard. If the keyboard is in ASCII ("cooked") mode, the usual editing keys are active. PTR BUFFER Receives keyboard input string PTR BUFFER Keyboard buffer information in a 4-byte structure: Offset Length Description 00H 2 Contains length of keyboard input buffer (maximum = 255) 02H 2 Contains zero or length of data in buffer to be edited; receives actual length of input WORD Wait/no-wait flag 0 Wait for input (see Notes) 1 Do not wait for input WORD Keyboard handle (default = 0) Notes: If the second word of the keyboard information structure is nonzero, the data in the keyboard input buffer is used as a template for editing. During input in ASCII mode, if the wait/no-wait flag = 0, the function blocks until the Enter key is pressed or the buffer is full. Wait/no-wait flag = 1 is not supported in ASCII mode. When KbdStringIn is called in binary mode and the wait/no-wait flag = 0, the function blocks until the buffer is full. (The Enter key receives no special handling and is placed in the buffer like any other key.) If the wait/no-wait flag = 1, the function returns immediately with as many waiting characters as will fit into the buffer. In real mode, the keyboard handle is ignored. Related functions: DosRead, KbdCharIn, KbdGetStatus, KbdPeek, KbdSetStatus. KbdSynch Serializes access to the keyboard device driver by multiple processes within a screen group. KbdSynch requests ownership of a system semaphore that is cleared each time a keyboard function completes. WORD Wait/no-wait flag 0 Wait for the semaphore 1 Do not wait for the semaphore Note: KbdSynch is intended for use by a keyboard subsystem and is not normally called by applications. Related function: DosDevIOCtl. KbdXlate Translates a scan code and its shift states into an ASCII character code. PTR BUFFER Contains a scan code in the second byte of a 16-byte keyboard translation record: Offset Length Description 00H 1 ASCII character code 01H 1 Scan code 02H 1 Status Bit(s) Significance (if set) 0 Shift status returned without character 14 Reserved (0) 5 On-the-spot conversion requested 6 Character is ready 7 Interim character 03H 1 Reserved (0) 04H 2 Keyboard shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 SysReq key down 06H 4 Time stamp (msec. since system was turned on or restarted) 08H 2 Keyboard device driver flags (see Notes) 0AH 2 Translation flags Bit(s) Significance (if set) 0 Translation complete 115 Reserved (0) 0CH 2 Translation shift state (initially set to 0; nonzero during a multibyte character translation; becomes 0 again when translation is completed) 0EH 2 Reserved (0) The calling process receives the translated ASCII character code in the first byte of the same structure. WORD Keyboard handle (default = 0) Notes: A process might need to call KbdXlate several times to complete a character, so the translation shift state parameter should not be modified unless a "fresh start" to translation is desired. The function clears the translation shift state when the character translation is complete. Keyboard device driver flags (in bytes 8 and 9 of the translation record) are defined as follows: Bit(s) Significance 05 Action code for driver (independent of scan code) 00H No special action 01H Keyboard acknowledge (ACK) 02H Secondary key prefix 03H Keyboard overrun 04H Resend request from keyboard 05H Reboot key combination 06H Stand-alone dump request 07H Shift key 08H Pause request (usually Ctrl-Num Lock) 09H Pseudo-pause request (usually Ctrl-S) 0AH Wake-up request (any key after a pause or pseudo-pause request) 0BH Invalid accent combination 0CH0FH Reserved 10H Accent key, affects following key translation 11H Break key (usually Ctrl-Break) 12H Pseudo-break key (usually Ctrl-C) 13H Print screen request 14H Print echo request (usually Ctrl-PrtSc) 15H Pseudo-print echo request (usually Ctrl-P) 16H Print flush request (usually Ctrl-Alt-PrtSc) 17H3FH Reserved 6 = 0 if key make (depression) = 1 if key break (release) 7 = 1 if prior key was secondary key prefix 8 = 1 if multimake (typematic repeated key) 9 = 1 if key was translated using the prior accent key 1013 Reserved (0) 1415 Used for communication between monitors Related function: KbdSetCustXt. MouClose Closes the mouse device and releases the logical mouse handle. If no other processes in the current screen group have a mouse handle open, the function removes the mouse pointer from the screen. WORD Mouse handle Related function: MouOpen. MouDeRegister [XPM] Restores the default mouse input routines for the current screen group; deregisters an alternative mouse subsystem that was previously installed with MouRegister. No parameters Related functions: KbdDeRegister, MouRegister, VioDeRegister. MouDrawPtr [XPM] Displays the mouse pointer on the screen. Any exclusion areas specified with a previous MouRemovePtr call are canceled. WORD Mouse handle Related functions: MouOpen, MouRemovePtr, MouSetDevStatus. MouFlushQue Discards any waiting mouse events for the current screen group. WORD Mouse handle Related functions: MouGetNumQueEl, MouOpen, MouReadEventQue. MouGetDevStatus Returns the status of the mouse driver. PTR WORD Receives mouse status Bit(s) Significance (if set) 0 Event queue is busy 1 Blocking read is in progress 2 Flush input queue in progress 3 Pointer-draw routine disabled because of unsupported display mode 47 Reserved (0) 8 Pointer-draw routine disabled 9 Mouse data is returned in relative mickeys rather than absolute screen coordinates 1015 Reserved (0) WORD Mouse handle Note: [1.1] If the application is running in a Presentation Manager window, the returned status is always zero. Related functions: MouGetNumMickeys, MouOpen, MouSetDevStatus. MouGetEventMask Returns the mouse driver's event mask for the current screen group. The mask defines the types of mouse interrupts that will generate an event record in the mouse driver's input queue. PTR WORD Receives event mask Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) WORD Mouse handle Note: Button 1 is defined as the left mouse button. Related functions: MouOpen, MouReadEventQue, MouSetEventMask. MouGetHotKey Returns a code indicating which mouse button or combination of mouse buttons is the system hot key. PTR WORD Receives hot key indicator Bit(s) Significance (if set) 0 No hot key defined (overrides bits 13) 1 Button 1 is hot key 2 Button 2 is hot key 3 Button 3 is hot key 415 Reserved (0) WORD Mouse handle Notes: Button 1 is the left mouse button. If multiple bits (other than bit 0) are set, the hot key is interpreted as a simultaneous pressing of the specified buttons. For example, if bits 1 and 2 are set, simultaneous pressing of buttons 1 and 2 is interpreted as the hot key. Related functions: MouGetNumButtons, MouOpen, MouSetHotKey. MouGetNumButtons Returns the number of buttons supported by the current screen group's mouse driver. PTR WORD Receives number of mouse buttons (13) WORD Mouse handle Related functions: MouGetHotKey, MouOpen. MouGetNumMickeys Returns the number of mickeys (units of mouse movement) per centimeter for the current screen group. PTR WORD Receives number of mickeys per centimeter of mouse travel WORD Mouse handle Related functions: MouGetDevStatus, MouOpen, MouSetDevStatus. MouGetNumQueEl Returns the number of mouse events for the current screen group that are waiting in the mouse driver's event queue. PTR BUFFER Receives mouse event queue information in the following format: Offset Length Description 00H 2 Number of waiting mouse events 02H 2 Maximum number of mouse events (queue size) WORD Mouse handle Related functions: MouFlushQue, MouOpen, MouReadEventQue. MouGetPtrPos Returns the screen position of the mouse pointer, using text or pixel coordinates as appropriate for the current display mode. Coordinate (0,0) is the upper left corner of the screen. PTR BUFFER Receives mouse pointer position in the following format: Offset Length Description 00H 2 y (row) coordinate 02H 2 x (column) coordinate WORD Mouse handle Note: [1.1] If the application is running in a Presentation Manager window, the pointer position is always returned in text coordinates. If the application window does not have the input focus, the function returns an error. Related functions: MouOpen, MouReadEventQue, MouSetPtrPos. MouGetPtrShape [XPM] Retrieves the mouse pointer shape for the current screen group. PTR BUFFER Receives mouse pointer shape in the form of AND and XOR masks that are meaningful to the pointer-draw routine PTR BUFFER Pointer definition data in the following format: Offset Length Description 00H 2 Contains the length of the buffer to receive the pointer shape; receives the length in bytes of the data used to build a mouse pointer image for each bit plane in the current display mode 02H 2 Receives columns in mouse pointer image 04H 2 Receives rows in mouse pointer image 06H 2 Receives column offset (from left) of the hot spot within the pointer image 08H 2 Receives row offset (from top) of the hot spot within the pointer image WORD Mouse handle Note: In text mode, the pointer width and height are always 1, and the row and column offsets of the hot spot are always 0. Related functions: MouOpen, MouSetPtrShape. MouGetScaleFact [XPM] Returns the scaling factors applied to mouse coordinates for the current screen group. The scaling factors define the number of mickeys (units of mouse movement) that are equivalent to eight pixels of screen movement. PTR BUFFER Receives scaling factors in the following format: Offset Length Description 00H 2 y (row) scaling factor (132,767; default = 8) 02H 2 x (column) scaling factor (132,767; default = 16) WORD Mouse handle Related functions: MouGetNumMickeys, MouOpen, MouSetScaleFact. MouInitReal Initializes the real mode mouse driver. PTR ASCIIZ Pathname of pointer-draw driver, or null pointer Notes: The pointer-draw driver must have been previously loaded by a DEVICE= statement in the CONFIG.SYS file, or a null pointer can be supplied to use the system default pointer-draw driver. This function is intended for use only by the Session Manager and should not ordinarily be called by application programs. Related function: MouOpen. MouOpen Opens the mouse device for the current screen group and returns a handle, which can be used for subsequent mouse operations. The mouse pointer is not visible until MouDrawPtr is called. PTR ASCIIZ Pathname of pointer-draw driver, or null pointer PTR WORD Receives mouse handle Notes: The default mouse state after a MouOpen operation is as follows: Row (y) scaling factor = 8 and column (x) scaling factor = 16 (see MouSetScaleFact) All events reported (see MouSetEventMask) Mouse event queue empty (see MouReadEventQue and MouGetNumQueEl) All settable mouse device status bits = 0 (see MouSetDevStatus) Pointer at the center of the screen (see MouSetPtrPos) Default pointer shape (see MouSetPtrShape) Exclusion area defined as the entire screen (see MouDrawPtr and MouRemovePtr) The pointer-draw driver must have been previously loaded by a DEVICE= statement in the CONFIG.SYS file, or a null pointer can be supplied to use the system default pointer-draw driver. Related functions: KbdOpen, MouClose, MouDrawPtr, MouSetPtrPos. MouReadEventQue Returns the mouse event data packet that is at the head of the mouse driver's input queue for the current screen group (that is, the oldest waiting event). The application can control which types of events generate records in the event queue with MouSetDevStatus. PTR BUFFER Receives mouse event data Offset Length Description 00H 2 Mouse event flags Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) 02H 4 Time stamp (msec. since system turned on or restarted) 06H 2 y (row) coordinate 08H 2 x (column) coordinate PTR WORD Contains wait/no-wait flag 0 Do not wait for mouse event; return all zeros for event data if no event is waiting 1 Wait for mouse event if none is ready WORD Mouse handle Notes: When an application is running full screen, the coordinates are expressed in character positions or pixels depending on the current display mode. When it is running in a Presentation Manager window, character coordinates are always returned. Button 1 is the left mouse button. Related functions: MouGetNumQueEl, MouGetPtrPos, MouOpen. MouRegister [XPM] Registers a replacement routine to service one or more mouse API functions for the current screen group. Functions which are not registered continue to be serviced by the Base Mouse Subsystem (BMSCALLS.DLL). PTR ASCIIZ Pathname of dynlink library PTR ASCIIZ Name of library entry point DWORD Identifies the mouse function(s) being registered Bit(s) Function (if set) 0 MouGetNumButtons 1 MouGetNumMickeys 2 MouGetDevStatus 3 MouGetNumQueEl 4 MouReadEventQue 5 MouGetScaleFact 6 MouGetEventMask 7 MouSetScaleFact 8 MouSetEventMask 9 MouGetHotKey 10 MouSetHotKey 11 MouOpen 12 MouClose 13 MouGetPtrShape 14 MouSetPtrShape 15 MouDrawPtr 16 MouRemovePtr 17 MouGetPtrPos 18 MouSetPtrPos 19 MouInitReal 20 MouSetDevStatus 2131 Reserved (0) Notes: A registered replacement for a mouse subsystem receives control with the following items on the stack: 32-bit MOUCALLS.DLL return address (top of stack) 16-bit application program's DS register 16-bit MOUCALLS.DLL internal return address 16-bit function code 0 MouGetNumButtons 1 MouGetNumMickeys 2 MouGetDevStatus 3 MouGetNumQueEl 4 MouReadEventQue 5 MouGetScaleFact 6 MouGetEventMask 7 MouSetScaleFact 8 MouSetEventMask 9 MouGetHotKey 10 MouSetHotKey 11 MouOpen 12 MouClose 13 MouGetPtrShape 14 MouSetPtrShape 15 MouDrawPtr 16 MouRemovePtr 17 MouGetPtrPos 18 MouSetPtrPos 19 MouInitReal 20 MouSetDevStatus 32-bit application return address Function-specific parameters placed on stack by application program The replacement routine must exit by a far return with the stack unchanged. The contents of AX are interpreted as follows: AX <> -1 Mou function performed, AX contains 0 or error code; return to application AX = -1 Replacement subsystem did nothing; invoke the appropriate base subsystem routine and return its status to the application Replacement mouse API routines can be registered by only one process per screen group at a time. MouRegister calls by other processes fail until the default mouse routines are restored with MouDeRegister. Related functions: KbdRegister, MouDeRegister, MouOpen, VioRegister. MouRemovePtr [XPM] Notifies the mouse driver not to draw the mouse pointer within the specified area. Such an exclusion area is cancelled with MouDrawPtr or replaced with another call to MouRemovePtr. PTR BUFFER Contains mouse pointer exclusion area definition in the following format: Offset Length Description 00H 2 Upper left y (row) coordinate 02H 2 Upper left x (column) coordinate 04H 2 Lower right y (row) coordinate 06H 2 Lower right x (column) coordinate WORD Mouse handle Notes: After a MouOpen call, the initial exclusion area is the entire screen (that is, the mouse pointer is not visible). Coordinates are expressed in character positions or pixels as appropriate for the current display mode. Related functions: MouDrawPtr, MouOpen, MouSetPtrShape. MouSetDevStatus [XPM] Configures the mouse driver for the current screen group. PTR WORD Contains mouse status flags Bit(s) Significance (if set) 07 Reserved (0) 8 Do not call pointer-draw routine at mouse interrupt time 9 Return mouse movements in relative mickeys rather than absolute screen coordinates 1015 Reserved (0) WORD Mouse handle Related functions: MouGetDevStatus, MouGetNumMickeys, MouOpen. MouSetEventMask Passes an event mask to the mouse driver for the current screen group. The mask defines the changes in mouse state that generate an event record in the driver's input queue. PTR WORD Contains event mask Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) WORD Mouse handle Note: If a specific event is not enabled, it can still be reported in the event records generated by those events that are enabled. Related functions: MouGetEventMask, MouOpen. MouSetHotKey Assigns a mouse button or combination of buttons as the system hot key. PTR WORD Contains hot key indicator Bit(s) Significance (if set) 0 No hot key defined (overrides bits 13) 1 Button 1 is hot key 2 Button 2 is hot key 3 Button 3 is hot key 415 Reserved (0) WORD Mouse handle Notes: Button 1 is the left mouse button. If multiple bits (other than bit 0) are set, the hot key is interpreted as a simultaneous pressing of the specified buttons. For example, if bits 1 and 2 are set, simultaneous pressing of buttons 1 and 2 is interpreted as the hot key. Only one process can set and reset the mouse hot key at a time. After a process has called MouSetHotKey with bit 0 of the hot key indicator = 0, requests from other processes fail until the same process has called MouSetHotKey with bit 0 of the hot key indicator = 1. Related functions: DosSystemService, MouGetHotKey, MouOpen. MouSetPtrPos Repositions the mouse pointer. Supply the coordinates as a character or pixel position according to the current display mode. Position (0,0) is the upper left corner of the screen. The mouse pointer is not displayed if the new position lies within an exclusion area defined with MouRemovePtr. PTR BUFFER Contains mouse pointer position in the following format: Offset Length Description 00H 2 y (row) coordinate 02H 2 x (column) coordinate WORD Mouse handle Note: [1.1] If the application is running in a Presentation Manager window, always specify the position in text coordinates. If the application window does not have the input focus, the function returns an error. Related functions: MouDrawPtr, MouGetPtrPos, MouOpen, MouRemovePtr. MouSetPtrShape [XPM] Defines the mouse pointer's shape and size for the current screen group. PTR BUFFER Contains mouse pointer shape in the form of AND and XOR masks that are meaningful to the pointer-draw routine PTR BUFFER Pointer definition data in the following format: Offset Length Description 00H 2 On call, contains the length of the buffer to receive the pointer shape. Upon return, receives the total length, in bytes, of data used to build a mouse pointer image for each bit plane in the current display mode 02H 2 Contains columns in mouse pointer image 04H 2 Contains rows in mouse pointer image 06H 2 Contains column offset (from left) of the hot spot within the pointer image 08H 2 Contains row offset (from top) of the hot spot within the pointer image WORD Mouse handle Notes: For text display modes, specify the mouse pointer shape by a single word: Bit(s) Significance 07 Character 810 Foreground color 11 Intensity 1214 Background color 15 Blinking In text mode, the pointer width and height are always 1, and the row and column offsets of the hot spot are always 0. Related functions: MouDrawPtr, MouGetPtrShape, MouOpen, MouRemovePtr. MouSetScaleFact [XPM] Assigns mouse scaling factors for the current screen group. The scaling factors define the number of mickeys (units of mouse movement) that constitute eight pixels of screen movement. PTR BUFFER Contains scaling factors in the following format: Offset Length Description 00H 2 y (row) scaling factor (132,767; default = 8) 02H 2 x (column) scaling factor (132,767; default = 16) WORD Mouse handle Related functions: MouGetNumMickeys, MouGetScaleFact, MouOpen. MouSynch Serializes access to the mouse device driver by multiple processes within a screen group. MouSynch requests ownership of a system semaphore that is cleared each time a mouse function completes. WORD Wait/no-wait flag 0 Wait for the semaphore 1 Do not wait for the semaphore Note: This function is intended for use by a mouse subsystem router (such as MOUCALLS.DLL) and should not ordinarily be called by application programs. Related function: DosDevIOCtl. VioAssociate [AVIO] [1.1] Associates a Vio presentation space with a screen device context. The output from subsequent calls to VioShowPS and VioShowBuf is directed to the specified context. DWORD Device context handle WORD Presentation space handle Notes: If the presentation space is currently associated with another device context, that link is broken. Similarly, if another presentation space is associated with the device context, that link is broken. If a NULL handle is supplied for the device context, the presentation space is disassociated from the device context. Related functions: VioCreatePS, VioShowBuf, VioShowPS. VioCreateLogFont [AVIO] [1.1] Creates a logical font for use with a Vio presentation space. The system chooses the available physical font that most closely matches the specified attributes. PTR BUFFER Contains font attribute information Offset Length Description 00H 2 Length of structure (64 bytes in OS/2 version 1.1) 02H 2 Font characteristics Bit Significance (if set) 0 Italic 1 Underline 2 Reverse 3 Outline (hollow) 4 Strikeout 04H 4 Match number from VioQueryFonts (0 = use best match) 08H 32 ASCIIZ typeface name 28H 2 Font registry number 2AH 2 Code page identifier 2EH 4 Maximum baseline extent 32H 4 Average character width 36H 2 Width class 38H 2 Weight class 3AH 2 Kerning/proportional spacing flags Bit(s) Significance (if set) 0 Reserved (0) 1 Nonproportional font 2 Kernable font (must be 0) 315 Reserved (0) 3CH 2 Font quality Bit(s) Significance (if set) 0 Default (must be 1) 1 Draft (must be 0) 2 Proof (must be 0) 315 Reserved (0) 3EH 2 Reserved (0) DWORD Local identifier for the font (13) PTR BUFFER Eight character name for logical font WORD Presentation space handle Note: If the entire attribute buffer is zero except for the code page, the default font for the specified code page is selected. Related function: VioQueryFonts. VioCreatePS [AVIO] [1.1] Creates a Vio presentation space of the specified dimensions and returns a handle. PTR WORD Receives presentation space handle WORD Height in character rows WORD Width in character columns WORD Presentation space format (0) WORD Number of attribute bytes (1 or 3) WORD Reserved (0) Notes: The size of the presentation space (width * height * (number of attribute bytes + 1)) must not exceed 32 KB. If the number of attributes parameter is 1, then the total storage for each character position in the presentation space is two bytes, with the following format: Offset Length Description 00H 1 Character code 01H 1 CGA text modecompatible attribute If the number of attributes parameter is 3, then the total storage for each character position in the presentation space is four bytes, with the following format: Offset Length Description 00H 1 Character code 01H 1 Foreground/background colors 02H 1 Underline, reverse, blink, font ID 03H 1 Spare (may be used by application) Related functions: VioAssociate, VioDestroyPS. VioDeleteSetID [AVIO] [1.1] Releases an identifier that was previously assigned to a logical font with VioCreateLogFont. DWORD Logical font identifier (-1, 1, 2, or 3) WORD Presentation space handle Note: If the logical font identifier is -1, the function releases all logical font identifiers. Related functions: VioCreateLogFont, VioCreatePS. VioDeRegister [XPM] Deactivates an alternative video subsystem that was previously registered for the current screen group. The default services provided by the Base Video Subsystem (BVSCALLS.DLL) for each Vio call are restored. No parameters Related functions: KbdDeRegister, MouDeRegister, VioRegister. VioDestroyPS [AVIO] [1.1] Destroys a Vio presentation space. WORD Presentation space handle Related function: VioCreatePS. VioEndPopUp Ends a video display popup operation by a background process; releases control of the physical screen and keyboard previously obtained with VioPopUp. WORD Video handle (0 = default) Related function: VioPopUp. VioGetAnsi Returns a flag indicating whether interpretation of ANSI escape sequences is enabled or disabled. PTR WORD Receives flag 0 ANSI support is disabled 1 ANSI support is enabled WORD Video handle (0 = default) Related functions: VioSetAnsi, VioWrtTTY. VioGetBuf [FAPI] Returns a pointer to the logical video buffer (LVB). Because each screen group has its own distinct LVB, a process can manipulate the buffer directly at any time without interfering with those of other screen groups. The process requests update of the physical display from the LVB by calling VioShowBuf. PTR DWORD Receives pointer to logical video buffer PTR WORD Receives length in bytes of logical video buffer WORD Video handle (0 = default) Note: The dimensions of the LVB (columns and rows) for the current display mode can be obtained from VioGetMode. Related functions: VioGetMode, VioGetPhysBuf, VioShowBuf. VioGetConfig [FAPI] Returns information about the video adapter and monitor. WORD Reserved (0) PTR BUFFER Receives video configuration data in the following format: Offset Length Description 00H 2 Length of structure (always 10 in OS/2 versions 1.0 and 1.1) 02H 2 Adapter type 0 = Monochrome Adapter (MDA) 1 = Color/Graphics Adapter (CGA) 2 = Enhanced Graphics Adapter (EGA) 3 = Video Graphics Array (VGA, PS/2) 46 = reserved 7 = 8514/A Display Adapter 04H 2 Display type 0 = Monochrome Display 1 = RGB Color Display 2 = Enhanced Color Display 3 = 8503 Analog Monochrome Display 4 = 8512 or 8513 Analog Color Display 58 = reserved 9 = 8514 Analog Color Display 06H 4 Bytes of memory on video adapter WORD Video handle (0 = default) Related functions: DosDevConfig, VioGetState, VioGetMode. VioGetCp Returns the identifier for the code page used by the video subsystem for the current screen group. WORD Reserved (0) PTR WORD Receives code page ID (0 = default ROM code page) WORD Video handle (0 = default) Related functions: DosGetCp, DosSetCp, KbdGetCp, VioSetCp. VioGetCurPos [FAPI] Returns the cursor position in text coordinates. Position (0,0) is the upper left corner of the display. PTR WORD Receives y (row) coordinate PTR WORD Receives x (column) coordinate WORD Video handle (0 = default) Related functions: VioGetCurType, VioSetCurPos. VioGetCurType [FAPI] Returns information about the cursor shape, size, and attribute. PTR BUFFER Receives cursor characteristics in the following format: Offset Length Description 00H 2 Cursor start line 02H 2 Cursor end line 04H 2 Cursor width (0 = default; 1 = maximum valid value in text modes) 06H 2 Cursor attribute (0 = visible; -1 = hidden) WORD Video handle (0 = default) Note: The valid values for the cursor starting and ending lines are determined by the character cell dimensions in the current display mode. You can calculate the size of the character cell from the information returned by VioGetMode. Related functions: VioGetCurPos, VioGetMode, VioSetCurType. VioGetDeviceCellSize [AVIO] [1.1] Returns the character cell height and width in pels (pixels) for the specified presentation space. PTR WORD Receives pel rows per character cell PTR WORD Receives pel columns per character cell WORD Presentation space handle Related functions: VioCreatePS, VioSetDeviceCellSize. VioGetFont [XPM] Returns the specified or current font table. The font is a bitmap that defines the appearance of each character on the screen. PTR BUFFER Contains information about the type of font request, receives information about font characteristics. Offset Length Description 00H 2 Contains length (always 14 bytes in OS/2 versions 1.0 and 1.1) 02H 2 Contains request type 0 = get current font 1 = get ROM (default) font 04H 2 Contains width of character cell in pixels for desired font (text modes); receives width of character cell for font (graphics modes) 06H 2 Contains height of character cell in pixels for desired font (text modes); receives height of character cell for font (graphics modes) 08H 4 Contains address of buffer to receive font, or null pointer; in latter case, receives pointer to font 0CH 2 Contains length of buffer to receive font; receives actual length of font WORD Video handle (0 = default) Notes: See VioSetFont for a list of the available fonts on each type of display adapter. If a null pointer is supplied for the font buffer, OS/2 allocates memory to hold the font and returns a pointer to memory in the same location in the structure. Related functions: VioGetCp, VioSetFont. VioGetMode [FAPI] Returns the characteristics of the current display mode, including the screen dimensions, number of displayable colors, and display mode type (text or graphics). PTR BUFFER Contains length of structure in the first word; receives display mode information in the following format: Offset Length Description 00H 2 Contains length (minimum 3, maximum 14 in OS/2 versions 1.0 and 1.1; lengths > 3 must be even) 02H 1 Display mode type Bit(s) Significance 0 0 = monochrome-compatible mode 1 = other 1 0 = text mode 1 = graphics mode 2 0 = enable color burst 1 = disable color burst 37 Reserved 03H 1 Displayable colors 0 = monochrome-compatible mode 1 = 2 colors 2 = 4 colors 4 = 16 colors 8 = 256 colors 04H 2 Horizontal (x) resolution in text columns 06H 2 Vertical (y) resolution in text rows 08H 2 Horizontal (x) resolution in pixels 0AH 2 Vertical (y) resolution in pixels 0CH 2 Reserved (0) WORD Video handle (0 = default) Note: On display adapters other than the CGA, the color burst bit is meaningless. Related functions: VioGetConfig, VioGetState, VioSetMode. VioGetOrg [AVIO] [1.1] Returns the coordinates of the character which is currently displayed at the upper left corner of the presentation space's window. PTR WORD Receives row (y) coordinate PTR WORD Receives column (x) coordinate WORD Presentation space handle Note: Coordinates returned are expressed in character columns and rows rather than in pels. Related functions: VioCreatePS, VioSetOrg. VioGetPhysBuf [XPM] [FAPI] Returns a selector or selectors which can be used to directly manipulate the video adapter's physical display buffer. This function, and the selectors it returns, can be used only while the process's screen group is in the foreground. Selectors are allocated from the process's LDT and should be released with DosFreeSeg when they are no longer needed. PTR BUFFER Contains physical address and length of video buffer; receives selector(s) for buffer in the following format: Offset Length Description 00H 4 Contains starting address of video buffer, specified as a 32-bit physical (linear) address 04H 4 Contains length of video buffer 08H n Receives n/2 selectors that map to video buffer (one selector per 64 KB) WORD Video handle (0 = default) Notes: The buffer addresses to be mapped must fall in the range A0000H through BFFFFH. The process should use VioScrLock to prevent session switching while it is altering the display buffer. Related functions: VioScrLock, VioScrUnLock. VioGetState [XPM] Returns current settings of the palette registers, the border (overscan) color, or the intensity/blink toggle. PTR BUFFER Contains request type; receives information about the video state (see Notes) WORD Video handle (0 = default) Notes: The request block for the palette registers has the following format: Offset Length Description 00H 2 Contains length of structure (maximum 38 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 0 = get palette registers 04H 2 Contains number of first palette register to return (015) 06H n Receives contents of n/2 palette registers, 16 bits per register The request block for border (overscan) color has the following format: Offset Length Description 00H 2 Contains length of structure (6 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 1 = get border color 04H 2 Receives border color The request block for the intensity/blink toggle has the following format: Offset Length Description 00H 2 Contains length of structure (6 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 2 = get intensity/blink toggle 04H 2 Receives toggle value 0 = blinking foreground colors are enabled 1 = intensified background colors are enabled Related functions: VioGetConfig, VioGetMode, VioSetState. VioModeUndo [XPM] Cancels a VioModeWait call issued by another thread within the same process. WORD Ownership option for VioModeWait 0 Retain ownership 1 Give up ownership WORD Termination option (for thread currently blocked in VioModeWait call) 0 Return error code to thread 1 Terminate thread WORD Reserved (0) Related function: VioModeWait. VioModeWait [XPM] Notifies the application to restore the state of the video adapter after a VioPopUp by another process. The thread calling VioModeWait is blocked until the restore operation is needed. The thread must then write the appropriate values to the adapter and immediately reissue the VioModeWait. WORD Request type 0 Notify application to restore its video mode at the end of a video pop-up by another process <> 0 Reserved (not used in OS/2 version 1.0 or 1.1) PTR WORD Receives code for the operation to be performed upon return from VioModeWait 0 Restore display mode <> 0 Reserved (not used in OS/2 version 1.0 or 1.1) WORD Reserved (0) Notes: File system and loader API functions should not be called by a VioModeWait thread; avoidance of such calls eliminates any chance of an attempted pop-up by the system's critical error handler. VioModeWait is used by applications which write directly to the control registers of the video adapter. Applications which only use Vio calls to set the video mode, palette registers, and so on need not use VioModeWait. OS/2 saves and restores the contents of the video display buffer across a pop-up. Graphics mode applications may need both a VioModeWait thread and a VioSavReDrawWait thread; the latter thread provides for saving and restoring both the state of the video adapter and the contents of the display buffer across switches between sessions. Related functions: VioModeUndo, VioSavRedrawUndo, VioSavRedrawWait. VioPopUp Asserts control of the physical screen and keyboard, regardless of which session is currently in the foreground. After a successful VioPopUp call, the requesting process can use other Vio and Kbd calls to interact with the operator. During a pop-up, Vio calls by other processes are blocked and session switching is disabled. PTR WORD Contains option flags for pop-up Bit(s) Significance 0 0 = return immediately with error code if pop-up is not available 1 = wait until pop-up is available 1 0 = nontransparent mode; display is placed into 80-by-25 text mode, the screen is cleared, and the cursor is placed in the upper left corner 1 = transparent mode; if the display is in an 80-by-25 text mode, no mode change occurs, and the screen contents and cursor position are not disturbed 215 Reserved (0) WORD Video handle (0 = default) Notes: Regardless of the mode (transparent or nontransparent), OS/2 always restores the contents of the screen at the time of the pop-up when the application issues VioEndPopUp. You cannot use the selector obtained from a previous VioGetPhysBuf call during a pop-up. You cannot use the following API calls during a pop-up: DosExecPgm VioModeWait VioRegister VioScrUnLock VioDeRegister VioPopUp VioSavRedrawUndo VioSetAnsi VioGetBuf VioPrtSc VioSavRedrawWait VioSetMode VioGetPhysBuf VioPrtScToggle VioScrLock VioShowBuf VioModeUndo Related function: VioEndPopUp. VioPrtSc Copies the contents of the screen to the printer. The Base Video Subsystem (BVSCALLS.DLL) does not support this function in graphics modes; a process can install an alternate routine with VioRegister. VioPrtSc is intended for use by the Task Manager and should not be called by application programs. WORD Video handle (0 = default) Related functions: None. VioPrtScToggle Enables or disables echoing of characters to the printer during processing of DosWrite (to standard output) or VioWrtTTY calls. VioPrtScToggle is intended for use by the Task Manager and should not be called by application programs. WORD Video handle (0 = default) Related functions: None. VioQueryFonts [AVIO] [1.1] Returns a list of the available fonts that match the specified typeface. PTR DWORD Contains the length of the buffer to receive the font metrics PTR BUFFER Receives one or more entries specifying the metrics of the matching fonts (see Notes) DWORD Local font identifier (13) PTR DWORD Receives the number of matching fonts PTR ASCIIZ Typeface name of desired fonts WORD Presentation space handle Notes: Unlike other kernel API functions, a nonzero value returned in AX does not necessarily indicate an error. The function returns the number of fonts not retrieved (because buffer space was insufficient), zero if all fonts were retrieved, or -1 if an error occurred. See GpiQueryFontMetrics in the Presentation Manager programmer's reference manual for the format of each font metrics entry. Related functions: VioCreateLogFont, VioCreatePS. VioQuerySetIds [AVIO] [1.1] Returns a list of all loaded fonts. PTR BUFFER Receives local identifiers PTR BUFFER Receives 8-character font names PTR BUFFER Receives object types for the fonts DWORD Number of local font identifiers in use WORD Presentation space handle Related functions: VioCreateLogFont, VioCreatePS. VioReadCellStr [FAPI] Reads a series of character-attribute pairs (cells) from the logical video buffer (LVB), starting at the specified position. PTR BUFFER Receives character-attribute pairs from the logical screen PTR WORD Contains the length (in bytes) of the buffer to receive data from display; receives the actual number of bytes transferred to the buffer WORD Starting y (row) screen coordinate WORD Starting x (column) screen coordinate WORD Video handle (0 = default) Notes: Each display position requires 2 bytes in the buffer. The character code is in the lower byte, and the character attribute is in the upper byte. Coordinate (x,y)=(0,0) is the upper left corner of the display. Video attributes and colors are listed on p. 107. Related functions: VioReadCharStr, VioWrtCellStr. VioReadCharStr [FAPI] Reads a string of characters from the logical video buffer (LVB), starting at the specified position. PTR BUFFER Receives character string PTR WORD Contains the length in bytes of the buffer; receives the actual number of bytes transferred to the buffer WORD Starting y (row) screen coordinate WORD Starting x (column) screen coordinate WORD Video handle (0 = default) Note: Coordinate (x,y)=(0,0) is the upper left corner of the display. Related functions: VioReadCellStr, VioWrtCharStr. VioRegister [XPM] Registers replacement routine(s) to service one or more video API functions for the current screen group. Unregistered functions continue to be serviced by the Base Video Subsystem (BVSCALLS.DLL). PTR ASCIIZ Pathname of dynlink library PTR ASCIIZ Name of library entry point DWORD Identifies video function(s) being registered Bit Function Registered (if set) 0 VioGetCurPos 1 VioGetCurType 2 VioGetMode 3 VioGetBuf 4 VioGetPhysBuf 5 VioSetCurPos 6 VioSetCurType 7 VioSetMode 8 VioShowBuf 9 VioReadCharStr 10 VioReadCellStr 11 VioWrtNChar 12 VioWrtNAttr 13 VioWrtNCell 14 VioWrtTTY 15 VioWrtCharStr 16 VioWrtCharStrAtt 17 VioWrtCellStr 18 VioScrollUp 19 VioScrollDn 20 VioScrollLf 21 VioScrollRt 22 VioSetAnsi 23 VioGetAnsi 24 VioPrtSc 25 VioScrLock 26 VioScrUnLock 27 VioSavRedrawWait 28 VioSavRedrawUndo 29 VioPopUp 30 VioEndPopUp 31 VioPrtScToggle DWORD Identifies video function(s) being registered Bit(s) Function Registered (if set) 0 VioModeWait 1 VioModeUndo 2 VioGetFont 3 VioGetConfig 4 VioSetCp 5 VioGetCp 6 VioSetFont 7 VioGetState 8 VioSetState 931 Reserved (0) Notes: A registered replacement for a video subsystem receives control with the following items on the stack: 32-bit VIOCALLS.DLL return address (top of stack) 16-bit application program's DS register 16-bit VIOCALLS.DLL internal return address 16-bit function code 0 VioGetPhysBuf 1 VioGetBuf 2 VioShowBuf 3 VioGetCurPos 4 VioGetCurType 5 VioGetMode 6 VioSetCurPos 7 VioSetCurType 8 VioSetMode 9 VioReadCharStr 10 VioReadCellStr 11 VioWrtNChar 12 VioWrtNAttr 13 VioWrtNCell 14 VioWrtCharStr 15 VioWrtCharStrAtt 16 VioWrtCellStr 17 VioWrtTTY 18 VioScrollUp 19 VioScrollDn 20 VioScrollLf 21 VioScrollRt 22 VioSetAnsi 23 VioGetAnsi 24 VioPrtSc 25 VioScrLock 26 VioScrUnLock 27 VioSavRedrawWait 28 VioSavRedrawUndo 29 VioPopUp 30 VioEndPopUp 31 VioPrtScToggle 32 VioModeWait 33 VioModeUndo 34 VioGetFont 35 VioGetConfig 36 VioSetCp 37 VioGetCp 38 VioSetFont 39 VioGetState 40 VioSetState 32-bit application return address Function-specific parameters placed on stack by application program The replacement routine must exit by a far return with the stack unchanged. The contents of AX are interpreted as follows: AX <> -1 Vio function performed, AX contains 0 or error code; return to application AX = -1 Replacement subsystem did nothing; invoke the appropriate base subsystem routine and return its status to the application Replacement video API routines can be registered by only one process per screen group at a time. VioRegister calls by other processes fail until the default video routines are restored with VioDeRegister. Related functions: KbdRegister, MouRegister, VioDeRegister. VioSavRedrawUndo [XPM] Cancels a VioSavRedrawWait call issued by another thread within the same process. WORD Ownership option for VioSavRedrawWait 0 Retain ownership 1 Give up ownership WORD Termination option (for thread that is currently blocked in VioSavRedrawWait call) 0 Return error code to thread 1 Terminate thread WORD Video handle (0 = default) Related functions: VioModeUndo, VioModeWait, VioSavRedrawWait. VioSavRedrawWait [XPM] Notifies the application when the video buffer and adapter state need to be saved or restored, by blocking the requesting thread until the save or restore operation is required. NonPresentation Manager applications which use graphics display modes must create a thread that calls VioSavRedrawWait, performs the appropriate save or restore upon return from the function, and then immediately reissues VioSavRedrawWait. WORD Save/redraw option 0 Notify application for both save and redraw operations 1 Notify application of redraw operations only PTR WORD Receives notification type 0 Save display and adapter state 1 Restore display and adapter state WORD Video handle (0 = default) Notes: Text mode applications do not need VioSavRedrawWait unless they write directly to the video adapter's registers; OS/2 always saves and restores the display buffer across session switches in text mode. Selectors obtained from a previous VioGetPhysBuf call can be used by the VioSavRedrawWait thread. However, do not call VioScrLock before accessing the display buffer. If an application begins execution in the background (for example, if it is invoked by the START command), the application might be notified to restore the screen before it has ever saved it. Related functions: VioModeUndo, VioModeWait, VioSavRedrawUndo. VioScrLock [FAPI] Locks access to the physical display for I/O. Used by a process to prevent screen switches while it manipulates the video adapter's physical display buffer or I/O ports. WORD Wait/no-wait flag 0 Return immediately if screen is not available 1 Wait until screen is available PTR BYTE Receives status of lock operation 0 Lock successful 1 Lock not successful (process is in background, or a pop-up is in progress; relevant only if wait/no-wait flag = 0) WORD Video handle (0 = default) Notes: If a session switch is requested, and a process which called VioScrLock does not release the screen with VioScrUnLock within a system-defined time limit, the session switch occurs anyway and the process is frozen until its session is restored to the foreground. The time limit is 30 seconds in OS/2 versions 1.0 and 1.1. In real mode, VioScrLock never returns an error. Related functions: VioGetPhysBuf, VioScrUnLock. VioScrollDn [FAPI] VioScrollLf VioScrollRt VioScrollUp Scrolls all or part of the logical display down (VioScrollDn), left (VioScrollLf), right (VioScrollRt), or up (VioScrollUp) by one or more lines or columns, filling the new lines or columns with the specified character and attribute. The area to be scrolled is identified by the text coordinates of its upper left and lower right corners, with (x,y)=(0,0) defined as the upper left corner of the display. WORD y (row) coordinate, upper left corner WORD x (column) coordinate, upper left corner WORD y (row) coordinate, lower right corner WORD x (column) coordinate, lower right corner WORD Number of lines or columns to scroll PTR WORD Contains character (lower byte) and attribute (upper byte) used to fill blanked lines or columns WORD Video handle (0 = default) Notes: If you call any of these functions with the upper left corner = (0,0), lower right corner = (-1,-1), and number of lines or columns to scroll = -1, the entire screen is filled with the specified character-attribute pair. Video attributes and colors are listed on p. 107. Related function: VioWrtNCell. VioScrUnLock [XPM] [FAPI] Releases lock on the physical display; cancels the effect of a previous VioScrLock call. Used only by processes that access the video adapter's physical display buffer and I/O ports directly. WORD Video handle (0 = default) Note: In real mode, VioScrUnLock never returns an error. Related function: VioScrLock. VioSetAnsi Enables or disables the interpretation of ANSI escape sequences for screen control and keyboard mapping. WORD On/off indicator 0 Disable ANSI support 1 Enable ANSI support (default condition) WORD Video handle (0 = default) Related functions: VioGetAnsi, VioWrtTTY. VioSetCp Selects the code page used by the video subsystem for the current screen group. The code page defines the character set and should not be confused with the font (which defines the appearance of the characters on the screen). WORD Reserved (0) WORD Code page ID (0 = default ROM code page) WORD Video handle (0 = default) Related functions: DosSetCp, VioGetCp, VioSetFont. VioSetCurPos [FAPI] Sets the cursor position in text coordinates. Position (0,0) is the upper left corner of the screen. WORD y (row) coordinate WORD x (column) coordinate WORD Video handle (0 = default) Related functions: VioGetCurPos, VioSetCurType. VioSetCurType [FAPI] Sets the cursor size, shape, and attribute. PTR BUFFER Contains cursor characteristics in the following format: Offset Length Description 00H 2 Cursor start line 02H 2 Cursor end line 04H 2 Cursor width 0 = default 1 = maximum legal value in text modes 06H 2 Cursor attribute 0 = visible -1 = hidden WORD Video handle (0 = default) Note: The valid values for the cursor starting and ending lines are determined by the character cell dimensions in the current display mode. You can calculate the size of the character cell from the information returned by VioGetMode. Related functions: VioGetCurType, VioGetMode, VioSetCurPos. VioSetDeviceCellSize [AVIO] [1.1] Sets the height and width of the character set in pels (pixels) for the specified presentation space. WORD Height in pels WORD Width in pels WORD Presentation space handle Related functions: VioCreatePS, VioQueryDeviceCellSize. VioSetFont [XPM] Downloads a display font into the video adapter (EGA, VGA, and PS/2 only). The font defines the appearance of each character on the screen and must be compatible with the current display mode. PTR BUFFER Contains information about the type of request and the address of the font table, as follows: Offset Length Description 00H 2 Length (always 14 bytes in OS/2 versions 1.0 and 1.1) 02H 2 Request type 0 = set current font <>0 = reserved 04H 2 Width of character cell in pixels 06H 2 Height of character cell in pixels 08H 4 Address of font table 0CH 2 Length in bytes of font table WORD Video handle (0 = default) Notes: The current code page is reset. Subsequent VioGetCp calls return an error until VioSetCp or DosSetCp is called. Valid fonts for the various graphics display adapters are indicated in the following table: Dimension CGA EGA VGA 8-by-8    8-by-14   8-by-16  9-by-14   9-by-16  Related functions: DosSetCp, VioGetFont, VioSetCp. VioSetMode [FAPI] Selects the display mode for the current screen group. The cursor is initialized to its default type and position. PTR BUFFER Contains display mode information in the following format: Offset Length Description 00H 2 Length (minimum 3, maximum 14 in OS/2 versions 1.0 and 1.1; if > 3 must be even) 02H 1 Display mode type Bit Significance 0 0 = monochrome-compatible mode 1 = other 1 0 = text mode 1 = graphics mode 2 0 = enable color burst 1 = disable color burst 37 Reserved 03H 1 Displayable colors 0 = monochrome-compatible mode 1 = 2 colors 2 = 4 colors 4 = 16 colors 8 = 256 colors 04H 2 Horizontal (x) resolution in text columns 06H 2 Vertical (y) resolution in text rows 08H 2 Horizontal (x) resolution in pixels 0AH 2 Vertical (y) resolution in pixels 0CH 2 Reserved (0) WORD Video handle (0 = default) Notes: Available text modes for the various adapters are the following: Columns (x) Rows (y) MDA CGA EGA VGA 40 25    80 25     80 43   80 50  Available graphics modes for the various adapters are the following: Columns (x) Rows (y) Colors CGA EGA VGA 320 200 4    320 200 16   320 200 256  640 200 2    640 200 16   640 350 2   640 350 4  640 350 16   640 480 2  640 480 16  On display adapters other than the CGA, the color burst bit is ignored. [1.1] If the application is running in a Presentation Manager window, only text modes can be selected. In real mode, the screen is also cleared. Related functions: VioGetMode, VioSetState. VioSetOrg [AVIO] [1.1] Sets the coordinates of the character to be displayed at the upper left corner of the presentation space's window. WORD Row (y) coordinate WORD Column (x) coordinate WORD Presentation space handle Note: Express the coordinates in character columns and rows rather than in pels. Related functions: VioCreatePS, VioGetOrg. VioSetState [XPM] Programs the palette registers, selects the border color, or sets the intensity/blink toggle for the current screen group. PTR BUFFER Contains video state information (see Notes) WORD Video handle (0 = default) Notes: The request block to set the palette registers has the following format: Offset Length Description 00H 2 Contains length of structure (maximum 38 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 0 = set palette registers 04H 2 Contains number of the first palette register to set (015) 06H n Contains color values for n/2 consecutive palette registers, 16 bits per register The request block to set the border (overscan) color has the following format: Offset Length Description 00H 2 Contains length of structure (6 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 1 = set border color 04H 2 Contains border color The request block to set the intensity/blink toggle has the following format: Offset Length Description 00H 2 Contains length of structure (6 in OS/2 versions 1.0 and 1.1) 02H 2 Contains 2 = set intensity/blink toggle 04H 2 Contains toggle value 0 = Enable blinking foreground colors 1 = Enable intensified background colors Related functions: VioGetState, VioSetMode. VioShowBuf [FAPI] Updates the physical display buffer from the logical video buffer (LVB). Called by processes that use the pointer returned by VioGetBuf to manipulate the LVB directly. If the process's screen group is in the background, the VioShowBuf call is ignored. WORD Byte offset of changed area within LVB WORD Length of changed area in LVB WORD Video handle (0 = default) Related function: VioGetBuf. VioShowPS [AVIO] [1.1] Updates the display from a Vio presentation space. WORD Height of region WORD Width of region WORD Offset of region WORD Presentation space handle Note: Express the height, width, and offset in character cells rather than in pels. The offset is relative to the first character in the presentation space and specifies the position of the upper left corner of the rectangle to be updated. Related function: VioCreatePS. VioWrtCellStr [FAPI] Writes a string of character-attribute pairs (cells) to the display, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. Control codes, including ANSI escape sequences, are ignored and appear as their character-graphics equivalents. PTR BUFFER Contains character-attribute pairs WORD Length in bytes of data to display WORD Starting y (row) coordinate WORD Starting x (column) coordinate WORD Video handle (0 = default) Notes: Each display position requires two bytes in the buffer. The character code is in the lower byte of a pair and the character attribute in the upper byte. Video attributes and colors are listed on p. 107. Related functions: VioReadCellStr, VioWrtCharStr, VioWrtCharStrAtt. VioWrtCharStr [FAPI] Writes a character string to the display, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. Control codes, including ANSI escape sequences, are ignored and appear on the display as their character-graphics equivalents. PTR BUFFER Contains character string WORD Number of characters WORD Starting y (row) coordinate WORD Starting x (column) coordinate WORD Video handle (0 = default) Related functions: VioReadCharStr, VioWrtCellStr, VioWrtCharStrAtt. VioWrtCharStrAtt [FAPI] Writes a character string to the display, applying the specified attribute to each character, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. Control codes, including ANSI escape sequences, are ignored and appear on the display as their character-graphics equivalents. PTR BUFFER Contains character string WORD Number of characters WORD Starting y (row) coordinate WORD Starting x (column) coordinate PTR BYTE Contains character attribute WORD Video handle (0 = default) Note: Video attributes and colors are listed on p. 107. Related functions: VioWrtCellStr, VioWrtCharStr. VioWrtNAttr [FAPI] Applies a character attribute to one or more characters on the display, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. PTR BYTE Contains attribute WORD Replication count WORD Starting y (row) coordinate WORD Starting x (column) coordinate WORD Video handle (0 = default) Note: Video attributes and colors are listed on p. 107. Related functions: VioWrtNCell, VioWrtNChar. VioWrtNCell [FAPI] Writes one or more copies of the same character-attribute pair (cell) to the display, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. PTR WORD Contains character in lower byte and attribute in upper byte WORD Replication count WORD Starting y (row) coordinate WORD Starting x (column) coordinate WORD Video handle (0 = default) Note: Video attributes and colors are listed on p. 107. Related functions: VioWrtNAttr, VioWrtNChar. VioWrtNChar [FAPI] Writes one or more copies of the same character to the display, starting at the specified position. Position (x,y)=(0,0) is the upper left corner of the display. The cursor position is not affected. PTR BYTE Contains character WORD Replication count WORD Starting y (row) coordinate WORD Starting x (column) coordinate WORD Video handle (0 = default) Related functions: VioWrtNAttr, VioWrtNCell. VioWrtTTY [FAPI] Writes a character string to the display in "teletype mode," beginning at the current cursor position. Line wrapping and screen scrolling are provided, and the cursor position is updated. Backspace, carriage return, linefeed, and bell are detected and handled appropriately. Tabs are expanded to spaces using 8-column tab stops. If ANSI support is enabled (the default condition), ANSI escape sequences for screen control and keyboard remapping have their expected effects. PTR BUFFER Contains character string WORD Length of character string WORD Video handle (0 = default) Related functions: DosPutMessage, DosWrite, VioGetAnsi, VioSetAnsi, VioWrtCellStr, VioWrtCharStr, VioWrtCharStrAttr. Part 2 DosDevIOCtl Functions The DosDevIOCtl functions are identified by category (for the associated device) and function number. The following table relates each category to the corresponding device and to the API function from which a device handle should be obtained. Category Device Handle From 1 Serial port DosOpen 2 Reserved for OS/2 3 Pointer device DosOpen 4 Keyboard DosOpen 5 Printer DosOpen 6 Light pen 7 Mouse MouOpen 8 Logical disk DosOpen 9 Physical disk DosPhysicalDisk 10 Character Device Monitor DosOpen 11 General Device Control DosOpen 12127 Reserved for OS/2 128255 Reserved for user programs and drivers Category 1 Function 41H DosDevIOCtl Set Baud Rate Parameter Buffer Format: WORD Baud rate (110, 150, 300, 600, 1200, 2400, 4800, 9600, or 19200) Data Buffer Format: None Use null pointer Category 1 Function 42H DosDevIOCtl Set Line Characteristics Parameter Buffer Format: BYTE Data bits (58) BYTE Parity (0 = none; 1 = odd; 2 = even; 3 = mark; 4 = space) BYTE Stop bits (0 = 1; 1 = 1.5; 2 = 2) Data Buffer Format: None Use null pointer Category 1 Function 44H DosDevIOCtl Transmit Byte Immediate Parameter Buffer Format: BYTE Character to be transmitted Data Buffer Format: None Use null pointer Category 1 Function 45H DosDevIOCtl Set Break Off Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD COM error word (see Category 1 Function 6DH, pp. 62728) Category 1 Function 46H DosDevIOCtl Set Modem Control Signals Parameter Buffer Format: BYTE Modem control signals ON mask BYTE Modem control signals OFF mask ON Mask OFF Mask Meaning 01H FFH Set DTR 00H FEH Clear DTR 02H FFH Set RTS 00H FDH Clear RTS 03H FFH Set DTR and RTS 00H FCH Clear DTR and RTS Data Buffer Format: WORD COM error word (see Category 1 Function 6DH, pp. 62728) Category 1 Function 47H DosDevIOCtl Stop Transmitting (as if XOFF received) Parameter Buffer Format: None Use null pointer Data Buffer Format: None Use null pointer Category 1 Function 48H DosDevIOCtl Start Transmitting (as if XON received) Parameter Buffer Format: None Use null pointer Data Buffer Format: None Use null pointer Category 1 Function 4BH DosDevIOCtl Set Break On Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD COM error word (see Category 1 Function 6DH, pp. 62728) Category 1 Function 53H DosDevIOCtl Set Device Control Block (DCB) Parameter Buffer Format: WORD Write timeout (in .01 seconds) WORD Read timeout (in .01 seconds) BYTE Flags byte 1 Bit Significance (if set) 0 Enable DTR control mode 1 Enable input handshaking using DTR 2 Reserved (0) 3 Enable output handshaking using CTS 4 Enable output handshaking using DSR 5 Enable output handshaking using DCD 6 Enable input sensitivity using DSR 7 Reserved (0) BYTE Flags byte 2 Bit(s) Significance (if set) 0 Enable XON/XOFF transmit flow control 1 Enable XON/XOFF receive flow control 2 Enable error replacement character 3 Enable null stripping 4 Enable break replacement character 5 Reserved (0) 67 RTS state 00 = disable RTS control mode 01 = enable RTS control mode 10 = enable input handshaking using RTS 11 = enable toggling on transmit mode BYTE Flags byte 3 Bit(s) Significance (if set) 0 Enable write infinite timeout processing 12 Read timeout processing 00 = invalid input 01 = normal read timeout processing 10 = wait for something, read timeout processing 11 = no wait, read timeout processing 37 Reserved (0) BYTE Error replacement character BYTE Break replacement character BYTE XON character BYTE XOFF character Data Buffer Format: None Use null pointer Category 1 Function 61H DosDevIOCtl Get Baud Rate Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Baud rate Category 1 Function 62H DosDevIOCtl Get Line Characteristics Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Data bits (58) BYTE Parity (0 = none; 1 = odd; 2 = even; 3 = mark; 4 = space) BYTE Stop bits (0 = 1; 1 = 1.5; 2 = 2) BYTE Break status (0 = break not being transmitted; 1 = break being transmitted) Category 1 Function 64H DosDevIOCtl Get COM Status Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE COM port status Bit Significance (if set) 0 Transmit waiting for CTS to be turned on 1 Transmit waiting for DSR to be turned on 2 Transmit waiting for DCD to be turned on 3 Transmit waiting because XOFF received 4 Transmit waiting because XOFF transmitted 5 Transmit waiting because break being transmitted 6 Character waiting to transmit immediately 7 Receive waiting for DSR to be turned on Category 1 Function 65H DosDevIOCtl Get Transmit Data Status Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Transmit data status Bit(s) Significance (if set) 0 Write request packets in progress or queued 1 Data in driver transmit queue 2 Data currently being transmitted 3 Character waiting to be transmitted immediately 4 Waiting to transmit XON character 5 Waiting to transmit XOFF character 67 Reserved Category 1 Function 66H DosDevIOCtl Get Modem Output Control Signals Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Modem output control signals Bit(s) Significance (if set) 0 Data terminal ready (DTR) 1 Request to send (RTS) 27 Reserved Category 1 Function 67H DosDevIOCtl Get Modem Input Control Signals Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Modem input control signals Bit(s) Significance (if set) 03 Reserved 4 Clear to send (CTS) 5 Data set ready (DSR) 6 Ring indicator (RI) 7 Data carrier detect (DCD) Category 1 Function 68H DosDevIOCtl Get Number of Characters in Receive Queue Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Number of waiting characters WORD Size of receive queue Category 1 Function 69H DosDevIOCtl Get Number of Characters in Transmit Queue Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Number of waiting characters WORD Size of transmit queue Category 1 Function 6DH DosDevIOCtl Get COM Error Information Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD COM error word Bit(s) Significance (if set) 0 Receive queue overrun 1 Receive hardware overrun 2 Parity error 3 Framing error 415 Reserved Category 1 Function 72H DosDevIOCtl Get COM Event Information Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD COM event information Bit(s) Significance (if set) 0 Character received and placed in queue 1 Reserved 2 Last character sent from transmit queue 3 Change in CTS state 4 Change in DSR state 5 Change in DCD state 6 Break detected 7 Parity, framing, or overrun error 8 Ring detected 915 Reserved Category 1 Function 73H DosDevIOCtl Get Device Control Block (DCB) Parameter Buffer Format: None Use null pointer Data Buffer Format: Same as parameter buffer format for Category 1 Function 53H (p. 624) Category 3 Function 72H DosDevIOCtl Get Pointer-Draw Routine Address Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Return code DWORD Pointer-draw routine entry point WORD Pointer-draw routine data segment selector Category 4 Function 50H DosDevIOCtl Set Code Page Parameter Buffer Format: DWORD Contains pointer to keyboard code page Data Buffer Format: None Use null pointer Category 4 Function 51H DosDevIOCtl Set Input Mode Parameter Buffer Format: BYTE Input mode; only bits 0 and 7 are significant: 0xxxxxx0 = ASCII mode 1xxxxxx0 = binary mode without shift reporting 1xxxxxx1 = binary mode with shift reporting Data Buffer Format: None Use null pointer Category 4 Function 52H DosDevIOCtl Set Interim Character Flags Parameter Buffer Format: BYTE Interim character flags Bit(s) Significance (if set) 04 Reserved (0) 5 On-the-spot conversion requested 6 Reserved (0) 7 Interim character flag on Data Buffer Format: None Use null pointer Category 4 Function 53H DosDevIOCtl Set Shift State Parameter Buffer Format: WORD Shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 Sys Req key down BYTE National language support (NLS) status (0 in USA) Data Buffer Format: None Use null pointer Category 4 Function 54H DosDevIOCtl Set Typematic Rate and Delay Parameter Buffer Format: WORD Delay in milliseconds WORD Repeat rate in characters per second Data Buffer Format: None Use null pointer Category 4 Function 55H DosDevIOCtl Notify Change of Foreground Session Parameter Buffer Format: WORD Foreground session number WORD Switch/terminate flag 0 = switching to specified session -1 = specified session is terminating Data Buffer Format: None Use null pointer Category 4 Function 56H DosDevIOCtl Set Task Manager Hot Key Parameter Buffer Format: WORD Shift state Bit(s) Significance (if set) 0 Right Shift key down 1 Left Shift key down 27 Reserved 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 Sys Req key down BYTE Scan code of hot key make (depression) BYTE Scan code of hot key break (release) WORD Hot key identifier (015) Data Buffer Format: None Use null pointer Category 4 Function 57H DosDevIOCtl Bind Logical Keyboard to Physical Keyboard Parameter Buffer Format: WORD Keyboard handle Data Buffer Format: None Use null pointer Category 4 Function 58H DosDevIOCtl Set Code Page Identifier Parameter Buffer Format: DWORD Pointer to code page WORD Code page ID WORD Reserved (must be 0) Data Buffer Format: None Use null pointer Category 4 Function 5CH DosDevIOCtl Set NLS and Custom Code Page Parameter Buffer Format: DWORD Pointer to code page WORD Code page number WORD Code page to load WORD Hot key identifier Data Buffer Format: None Use null pointer Category 4 Function 5DH DosDevIOCtl Create Logical Keyboard Parameter Buffer Format: WORD Handle to assign to new keyboard (0 = default keyboard) Data Buffer Format: None Use null pointer Category 4 Function 5EH DosDevIOCtl Destroy Logical Keyboard Parameter Buffer Format: WORD Handle for keyboard (0 = default keyboard) Data Buffer Format: None Use null pointer Category 4 Function 71H DosDevIOCtl Get Input Mode Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Input mode; only bits 0 and 7 are significant: 0xxxxxx0 = ASCII mode 1xxxxxx0 = binary mode without shift reporting 1xxxxxx1 = binary mode with shift reporting Category 4 Function 72H DosDevIOCtl Get Interim Character Flags Parameter Buffer Format: None Use null pointer Data Buffer Format: BYTE Interim character flags Bit(s) Significance (if set) 04 Reserved (0) 5 On-the-spot conversion requested 6 Reserved (0) 7 Interim character flag on Category 4 Function 73H DosDevIOCtl Get Shift State Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 Sys Req key down BYTE National language support (NLS) status (0 in USA) Category 4 Function 74H DosDevIOCtl Read Character Data Records Parameter Buffer Format: WORD Transfer count Bit(s) Significance 014 Number of records requested 15 Wait/no-wait flag 0 = wait if necessary for requested number of records 1 = do not wait; transfer only records already waiting Receives the actual number of character records in the same word Data Buffer Format: Receives 10 bytes per character as follows: BYTE ASCII character code BYTE Scan code BYTE Status Bit(s) Significance (if set) 0 Shift status returned without character (valid only in binary mode when shift reporting is turned on) 14 Reserved (0) 5 On-the-spot conversion requested 6 Character ready 7 Interim character BYTE Reserved (0) WORD Keyboard shift state Bit Significance (if set) 0 Right Shift key down 1 Left Shift key down 2 Either Ctrl key down 3 Either Alt key down 4 Scroll Lock on 5 Num Lock on 6 Caps Lock on 7 Insert on 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 Sys Req key down DWORD Time stamp (msec. since system was turned on or restarted) Category 4 Function 75H DosDevIOCtl Peek Character Data Record Parameter Buffer Format: WORD Keyboard input status Bit(s) Significance 0 0 = no character waiting 1 = at least 1 character waiting 114 Reserved (0) 15 0 = ASCII ("cooked") mode 1 = binary ("raw") mode Data Buffer Format: Same as for Category 4 Function 74H (p. 635) Category 4 Function 76H DosDevIOCtl Get Task Manager Hot Key Parameter Buffer Format: WORD Contains 0 to obtain maximum number of hot keys supported by driver, 1 to obtain current number of hot keys; receives maximum or current number of hot keys in same location Data Buffer Format: If parameter buffer contains 1 on call, data buffer receives 6 bytes per hot key in the following format: WORD Shift state Bit(s) Significance (if set) 0 Right Shift key down 1 Left Shift key down 27 Reserved 8 Left Ctrl key down 9 Left Alt key down 10 Right Ctrl key down 11 Right Alt key down 12 Scroll Lock key down 13 Num Lock key down 14 Caps Lock key down 15 Sys Req key down BYTE Scan code of hot key make (depression) BYTE Scan code of hot key break (release) WORD Hot key identifier (015) Category 4 Function 77H DosDevIOCtl Get Keyboard Type Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Keyboard type 0 = PC/AT keyboard 1 = Enhanced keyboard DWORD Reserved (0) Category 4 Function 78H DosDevIOCtl Get Code Page ID Parameter Buffer Format: WORD Current code page ID Data Buffer Format: None Use null pointer Category 4 Function 79H DosDevIOCtl Translate Scan Code to ASCII Parameter Buffer Format: WORD Code page ID Data Buffer Format: 10 BYTES Character data record (see KbdCharIn) WORD Keyboard device driver flags (see KbdXlate) WORD Translate flags Bit(s) Significance (if set) 0 Translation complete 115 Reserved WORD Translate state word 1 WORD Translate state word 2 Category 5 Function 42H DosDevIOCtl Set Frame Control Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Characters per line (80 or 132) BYTE Lines per inch (6 or 8) Category 5 Function 44H DosDevIOCtl Set Infinite Retry Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Retry state 0 = disable infinite retry 1 = enable infinite retry Category 5 Function 46H DosDevIOCtl Initialize Printer Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 5 Function 48H DosDevIOCtl Activate Font Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: WORD Code page ID WORD Font ID Category 5 Function 62H DosDevIOCtl Get Frame Control Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Characters per line (80 or 132) BYTE Lines per inch (6 or 8) Category 5 Function 64H DosDevIOCtl Return Infinite Retry Mode Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Retry mode 0 = infinite retry disabled 1 = infinite retry enabled Category 5 Function 66H DosDevIOCtl Return Printer Status Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Printer status Bit(s) Significance (if set) 0 Timed out 12 Reserved 3 I/O error 4 Printer selected 5 Out of paper 6 Acknowledge 7 Printer not busy Category 5 Function 69H DosDevIOCtl Query Active Font Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: WORD Current code page ID WORD Current font ID Category 5 Function 6AH DosDevIOCtl Verify Code Page and Font Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: WORD Current code page ID WORD Current font ID Category 7 Function 50H DosDevIOCtl Enable Pointer Drawing After Session Switch Parameter Buffer Format: None Use null pointer Data Buffer Format: None Use null pointer Category 7 Function 51H DosDevIOCtl Notify of Display Mode Change Parameter Buffer Format: WORD Length of structure (16 maximum) BYTE Display mode type Bit(s) Significance 0 0 = monochrome compatible mode 1 = other 1 0 = text mode 1 = graphics mode 2 0 = color burst enabled 1 = color burst disabled 37 Reserved (0) BYTE Number of colors 0 = monochrome compatible 1 = 2 colors 2 = 4 colors 4 = 16 colors 8 = 256 colors WORD Number of text columns WORD Number of text rows WORD Number of pixel columns WORD Number of pixel rows 2 BYTES Reserved Data Buffer Format: None Use null pointer Category 7 Function 52H DosDevIOCtl Notify of Impending Session Switch Parameter Buffer Format: WORD Session number WORD Action -1 Specified session is terminating >= 0 Specified session is being selected Data Buffer Format: None Use null pointer Category 7 Function 53H DosDevIOCtl Set Mouse Scaling Factors Parameter Buffer Format: WORD Scaling factor for row (y) coordinates (032,767; default = 8) WORD Scaling factor for column (x) coordinates (032,767; default = 16) Data Buffer Format: None Use null pointer Category 7 Function 54H DosDevIOCtl Set Mouse Event Mask Parameter Buffer Format: WORD Mouse event mask Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) Data Buffer Format: None Use null pointer Category 7 Function 55H DosDevIOCtl Set Mouse Button for System Hot Key Parameter Buffer Format: WORD Hot key identifier Bit(s) Significance (if set) 0 No hot key (overrides bits 13) 1 Button 1 is system hot key 2 Button 2 is system hot key 3 Button 3 is system hot key 415 Reserved (0) Data Buffer Format: None Use null pointer Category 7 Function 56H DosDevIOCtl Set Mouse Pointer Shape Parameter Buffer Format: WORD Pointer image buffer length WORD Columns in pointer image WORD Rows in pointer image WORD Column offset (from left) of hot spot WORD Row offset (from top) of hot spot Data Buffer Format: BUFFER Pointer image data (see MouSetPtrShape) Category 7 Function 57H DosDevIOCtl Cancel Exclusion Area and Draw Mouse Pointer Parameter Buffer Format: None Use null pointer Data Buffer Format: None Use null pointer Category 7 Function 58H DosDevIOCtl Set Exclusion Area for Mouse Pointer Parameter Buffer Format: WORD Upper left y coordinate WORD Upper left x coordinate WORD Lower right y coordinate WORD Lower right x coordinate Data Buffer Format: None Use null pointer Category 7 Function 59H DosDevIOCtl Set Mouse Pointer Position Parameter Buffer Format: WORD Row (y) coordinate WORD Column (x) coordinate Data Buffer Format: None Use null pointer Category 7 Function 5AH DosDevIOCtl Specify Address of Protected Mode Pointer-Draw Routine Parameter Buffer Format: DWORD Pointer-draw routine's entry point WORD Reserved (0) WORD Pointer-draw routine's data segment selector Data Buffer Format: None Use null pointer Category 7 Function 5BH DosDevIOCtl Specify Address of Real Mode Pointer-Draw Routine Parameter Buffer Format: DWORD Pointer-draw routine's entry point WORD Reserved (0) WORD Pointer-draw routine's data segment Data Buffer Format: None Use null pointer Category 7 Function 5CH DosDevIOCtl Set Mouse Driver Status Parameter Buffer Format: WORD Mouse driver status Bit(s) Significance 07 Reserved (0) 8 0 = pointer drawing enabled 1 = pointer drawing disabled 9 0 = mouse data returned in screen coordinates 1 = mouse data returned in mickeys 1015 Reserved (0) Data Buffer Format: None Use null pointer Category 7 Function 60H DosDevIOCtl Get Number of Mouse Buttons Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Number of mouse buttons (13) Category 7 Function 61H DosDevIOCtl Get Number of Mickeys per Centimeter Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Mickeys per centimeter of mouse travel Category 7 Function 62H DosDevIOCtl Get Mouse Driver Status Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Mouse driver status Bit(s) Significance (if set) 0 Mouse event queue busy 1 Blocking read in progress 2 Flush event queue in progress 3 Pointer drawing disabled due to unsupported mode 47 Reserved (0) 8 Pointer drawing disabled 9 Mouse data returned in mickeys rather than in screen coordinates 1015 Reserved (0) Category 7 Function 63H DosDevIOCtl Read Mouse Event Queue Parameter Buffer Format: WORD Wait/no-wait flag 0 = do not wait 1 = wait until mouse event available Data Buffer Format: WORD Mouse event flags Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) DWORD Time stamp (msec. since system was turned on or restarted) WORD y (row) coordinate WORD x (column) coordinate Category 7 Function 64H DosDevIOCtl Get Mouse Event Queue Status Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Number of events in queue WORD Size of event queue Category 7 Function 65H DosDevIOCtl Get Mouse Driver Event Mask Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Mouse event mask Bit(s) Significance (if set) 0 Mouse movement, no buttons down 1 Mouse movement, button 1 down 2 Button 1 down 3 Mouse movement, button 2 down 4 Button 2 down 5 Mouse movement, button 3 down 6 Button 3 down 715 Reserved (0) Category 7 Function 66H DosDevIOCtl Get Mouse Scaling Factors Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Scaling factor for row (y) coordinates (032,767; default = 8) WORD Scaling factor for column (x) coordinates (032,767; default = 16) Category 7 Function 67H DosDevIOCtl Get Mouse Pointer Position Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Row (y) coordinate WORD Column (x) coordinate Category 7 Function 68H DosDevIOCtl Get Mouse Pointer Shape Parameter Buffer Format: WORD Pointer image buffer length WORD Columns in pointer image WORD Rows in pointer image WORD Column offset (from left) of hot spot WORD Row offset (from top) of hot spot Data Buffer Format: BUFFER Pointer image data (see MouSetPrtShape) Category 7 Function 69H DosDevIOCtl Get Mouse Button Used as System Hot Key Parameter Buffer Format: None Use null pointer Data Buffer Format: WORD Hot key identifier Bit(s) Significance (if set) 0 No hot key (overrides bits 13) 1 Button 1 is system hot key 2 Button 2 is system hot key 3 Button 3 is system hot key 415 Reserved (0) Category 8 Function 00H DosDevIOCtl Lock Logical Drive Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 8 Function 01H DosDevIOCtl Unlock Logical Drive Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 8 Function 02H DosDevIOCtl Redetermine Media Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 8 Function 03H DosDevIOCtl Set Logical Drive Map Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Called with logical drive number (1 = A, 2 = B, etc.) Category 8 Function 20H DosDevIOCtl Check if Block Device Removable Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Status 0 = removable 1 = nonremovable Category 8 Function 21H DosDevIOCtl Get Logical Drive Map Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: BYTE Receives currently mapped logical drive (1 = A, 2 = B, etc.), or zero if only one logical drive is mapped to the physical drive Category 8 Function 43H DosDevIOCtl Set Device Parameters Parameter Buffer Format: BYTE Command information Bit(s) Significance 01 00 = get BPB from medium 01 = change default BPB for device 10 = use specified BPB 27 Reserved (0) Data Buffer Format: 31 BYTES Extended BPB WORD Bytes per sector BYTE Sectors per cluster WORD Reserved sectors BYTE Number of file allocation tables WORD Number of root directory entries WORD Total sectors BYTE Medium descriptor WORD Sectors per file allocation table WORD Sectors per track WORD Number of heads DWORD Number of hidden sectors DWORD Large total sectors 6 BYTES Reserved (0) WORD Number of cylinders BYTE Device type 0 = 360 KB 5.25" floppy disk drive 1 = 1.2 MB 5.25" floppy disk drive 2 = 3.5" floppy disk drive 3 = 8" single-density floppy disk drive 4 = 8" double-density floppy disk drive 5 = Fixed disk 6 = Tape drive 7 = Other WORD Device attributes Bit(s) Significance 0 0 = removable medium 1 = nonremovable medium 1 0 = no change-line support 1 = change-line supported 215 Reserved (0) Category 8 Function 44H DosDevIOCtl Write Track Parameter Buffer Format: BYTE Command information Bit(s) Significance 0 0 = track contains nonconsecutive sectors or does not start with 1 1 = track starts with 1 and contains only consecutive sectors 17 Reserved (0) WORD Head WORD Cylinder WORD First sector WORD Number of sectors DWORDS Track layout field, with a DWORD for each sector; the first word is the sector number, and the second is the sector size in bytes Data Buffer Format: BUFFER Data to be written Category 8 Function 45H DosDevIOCtl Format and Verify Track Parameter Buffer Format: BYTE Command information Bit(s) Significance 0 0 = track contains nonconsecutive sectors or does not start with 1 1 = track starts with 1 and contains only consecutive sectors 17 Reserved (0) WORD Head WORD Cylinder WORD Reserved (0) WORD Number of sectors DWORDS Track formatting table, with 4 bytes for each sector in the following order: cylinder, head, sector ID, and bytes per sector code (0 = 128 bytes, 1 = 256 bytes, 2 = 512 bytes, 3 = 1024 bytes) Data Buffer Format: None Use null pointer Category 8 Function 63H DosDevIOCtl Get Device Parameters Parameter Buffer Format: BYTE Command information Bit(s) Significance 0 0 = Return recommended BPB for drive 1 = Return current BPB for drive 17 Reserved (0) Data Buffer Format: Same as data buffer format for Category 8 Function 43H (p. 651) Category 8 Function 64H DosDevIOCtl Read Track Parameter Buffer Format: BYTE Command information Bit(s) Significance 0 0 = track contains nonconsecutive sectors or does not start with 1 1 = track starts with 1 and contains only consecutive sectors 17 Reserved (0) WORD Head WORD Cylinder WORD First sector WORD Number of sectors DWORDS Track layout field, with a DWORD for each sector; the first word is the sector number, and the second is the sector size in bytes Data Buffer Format: BUFFER Receives data from disk Category 8 Function 65H DosDevIOCtl Verify Track Parameter Buffer Format: Same as parameter buffer format for the preceding function, Category 8 Function 64H Data Buffer Format: None Use null pointer Category 9 Function 00H DosDevIOCtl Lock Physical Drive Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 9 Function 01H DosDevIOCtl Unlock Physical Drive Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 9 Function 44H DosDevIOCtl Write Physical Track Parameter Buffer Format: Same as parameter buffer format for Category 8 Function 44H (p. 652) Data Buffer Format: BUFFER Data to be written Category 9 Function 63H DosDevIOCtl Get Physical Device Parameters Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: WORD Reserved (0) WORD Number of cylinders WORD Number of heads WORD Sectors per track 8 BYTES Reserved (0) Category 9 Function 64H DosDevIOCtl Read Physical Track Parameter Buffer Format: Same as parameter buffer format for Category 8 Function 64H (p. 653) Data Buffer Format: BUFFER Receives data from disk Category 9 Function 65H DosDevIOCtl Verify Physical Track Parameter Buffer Format: Same as parameter buffer format for Category 8 Function 64H (p. 653) Data Buffer Format: None Use null pointer Category 10 Function 40H DosDevIOCtl Register Monitor Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: WORD Placement 0 = No preference 1 = Head of chain 2 = Tail of chain WORD Monitor index (driver-dependent, usually screen group) DWORD Address of monitor input buffer WORD Offset of monitor output buffer Category 11 Function 01H DosDevIOCtl Flush Input Buffer Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 11 Function 02H DosDevIOCtl Flush Output Buffer Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Category 11 Function 60H DosDevIOCtl Query Monitor Support Parameter Buffer Format: BYTE Command information (should be 0) Data Buffer Format: None Use null pointer Part 3 DevHlp FUNCTIONS The entries in this section describe each of the kernel Device Helper (DevHlp) functions available in OS/2 versions 1.0 and 1.1. These functions help OS/2 device drivers manage memory, CPU execution mode, request queues, ring buffers for character device I/O, monitors, and interrupts. The icons in the entry headers indicate the circumstances in which a particular DevHlp function can be used; they have the following meanings: [K] Function can be called when driver is in kernel mode; that is, when its strategy routine is called with a command code other than 0. [Int] Function can be called while the driver is servicing a hardware interrupt. [U] Function can be called in user mode; that is, it can be called while the driver is servicing a software interrupt by a real mode process. [Init] Function can be called during a driver's initialization; that is, it can be called when the driver's strategy routine is called with command code 0. [1.1] Function not available prior to OS/2 version 1.1. Device drivers invoke DevHlp functions by a far call to a common entry point. The entry point is passed to the driver by the kernel in an Initialize request packet when the driver is loaded. Unlike the OS/2 API used by applications, the DevHlp interface is register based. A particular DevHlp function is selected by the value in DL; other registers are used to pass function-specific values or addresses. A typical DevHlp call takes the following form: dhptr dd ? ; DevHlp entry point from ; Initialize request packet . . . mov dl,function ; selects DevHlp function . ; load other registers . ; with function-specific . ; parameters call [dhptr] ; indirect far call to ; DevHlp entry point Results, if any, from a DevHlp function are usually returned in CPU flags and registers. Most functions clear the Carry flag to signal success and set the Carry flag to signal an error. DevHlp Functions Listed Alphabetically Function Name Hex Dec Description ABIOSCall 36H 54 Invokes an ABIOS service ABIOSCommonEntry 37H 55 Invokes an ABIOS common entry point AllocGDTSelector 2DH 45 Allocates one or more GDT selectors AllocPhys 18H 24 Allocates a fixed block of memory AllocReqPacket 0DH 13 Allocates a block of memory large enough to contain the largest defined request packet AttachDD 2AH 42 Returns IDC entry point to a specified driver Block 04H 04 Suspends the requesting thread DeRegister 21H 33 Removes monitor from a monitor chain DevDone 01H 01 Signals completion of a request packet EOI 31H 49 Issues an End-Of-Interrupt to the 8259 PIC FreeLIDEntry 35H 53 Releases the logical ID for a device FreePhys 19H 25 Releases memory allocated by a previous call to AllocPhys FreeReqPacket 0EH 14 Releases memory previously allocated by a call to AllocReqPacket GetDOSVar 24H 36 Returns a bimodal pointer to a kernel variable GetLIDEntry 34H 52 Obtains a logical ID for a device Lock 13H 19 Locks a memory segment MonFlush 23H 35 Removes all data from a monitor chain MonitorCreate 1FH 31 Creates an empty monitor chain or destroys a monitor chain MonWrite 22H 34 Writes a data record into the monitor chain PhysToGDTSelector 2EH 46 Maps a 32-bit physical address to a GDT selector PhysToUVirt 17H 23 Converts a 32-bit physical address to a user virtual address PhysToVirt 15H 21 Converts a 32-bit physical address to a virtual address ProtToReal 30H 48 Switches the processor from protected mode into real mode PullParticular 0BH 11 Removes the specified request packet from the driver's request packet queue PullReqPacket 0AH 10 Removes the next waiting request packet from the driver's request packet queue PushReqPacket 09H 09 Adds a request packet to the driver's request packet queue QueueFlush 10H 16 Discards all data in the queue QueueInit 0FH 15 Initializes a character queue QueueRead 12H 18 Returns and removes the oldest waiting character from a character queue QueueWrite 11H 17 Adds a character to a character queue RealToProt 2FH 47 Switches the processor from real mode to protected mode Register 20H 32 Adds a device monitor to a device monitor chain RegisterStackUsage 38H 56 Indicates expected stack usage of device driver to interrupt manager ResetTimer 1EH 30 Removes a timer handler for the device driver ROMCritSection 26H 38 Protects a driver's handler from preemption Run 05H 05 Releases blocked threads SchedClockAddr 00H 00 Gets kernel's clock tick entry point SemClear 07H 07 Clears a RAM or system semaphore SemHandle 08H 08 Returns a system semaphore handle that can be used at task time or interrupt time SemRequest 06H 06 Claims a system or RAM semaphore SendEvent 25H 37 Notifies kernel that a key requiring special processing has been detected SetIRQ 1BH 27 Captures the hardware interrupt vector for the specified IRQ level SetROMVector 1AH 26 Replaces a real mode software interrupt handler with the driver's handler SetTimer 1DH 29 Adds a timer handler to the list of the system's timer handlers SortReqPacket 0CH 12 Inserts a read or write request packet into a request packet queue TCYield 03H 03 Yields the CPU so that time-critical threads can execute TickCount 33H 51 Adds a timer handler to the system's list of timer handlers Unlock 14H 20 Unlocks a memory segment UnPhysToVirt 32H 50 Notifies the kernel that virtual addresses are no longer needed UnSetIRQ 1CH 28 Releases ownership of a hardware interrupt VerifyAccess 27H 39 Verifies a process's right of access to memory VirtToPhys 16H 22 Converts a virtual address to a 32-bit physical address Yield 02H 02 Yields the CPU so that threads with the same or higher priority can execute DevHlp Functions Listed Numerically Hex Dec Function Name Description 00H 00 SchedClockAddr Gets kernel's clock tick entry point 01H 01 DevDone Signals completion of a request packet 02H 02 Yield Yields the CPU so that threads with the same or higher priority can execute 03H 03 TCYield Yields the CPU so that time-critical threads can execute 04H 04 Block Suspends the requesting thread 05H 05 Run Releases blocked threads 06H 06 SemRequest Claims a system or RAM semaphore 07H 07 SemClear Clears a RAM or system semaphore 08H 08 SemHandle Returns a system semaphore handle that can be used at task time or interrupt time 09H 09 PushReqPacket Adds a request packet to the driver's request packet queue 0AH 10 PullReqPacket Removes the next waiting request packet from the driver's request packet queue 0BH 11 PullParticular Removes the specified request packet from the driver's request packet queue 0CH 12 SortReqPacket Inserts a read or write request packet into a request packet queue 0DH 13 AllocReqPacket Allocates a block of memory large enough to contain the largest defined request packet 0EH 14 FreeReqPacket Releases memory previously allocated by a call to AllocReqPacket. 0FH 15 QueueInit Initializes a character queue 10H 16 QueueFlush Discards all data in the queue 11H 17 QueueWrite Adds a character to a character queue 12H 18 QueueRead Returns and removes the oldest waiting character from a character queue 13H 19 Lock Locks a memory segment 14H 20 Unlock Unlocks a memory segment 15H 21 PhysToVirt Converts a 32-bit physical address to a virtual address 16H 22 VirtToPhys Converts a virtual address to a 32-bit physical address 17H 23 PhysToUVirt Converts a 32-bit physical address to a user virtual address 18H 24 AllocPhys Allocates a fixed block of memory 19H 25 FreePhys Releases memory allocated by a previous call to AllocPhys 1AH 26 SetROMVector Replaces a real mode software interrupt handler with the driver's handler 1BH 27 SetIRQ Captures the hardware interrupt vector for the specified IRQ level 1CH 28 UnSetIRQ Releases ownership of a hardware interrupt 1DH 29 SetTimer Adds a timer handler to the list of the system's timer handlers 1EH 30 ResetTimer Removes a timer handler for the device driver 1FH 31 MonitorCreate Creates an empty monitor chain or destroys a monitor chain 20H 32 Register Adds a device monitor to a device monitor chain 21H 33 DeRegister Removes monitor from a monitor chain 22H 34 MonWrite Writes a data record into the monitor chain 23H 35 MonFlush Removes all data from a monitor chain 24H 36 GetDOSVar Returns a bimodal pointer to a kernel variable 25H 37 SendEvent Notifies kernel that a key requiring special processing has been detected 26H 38 ROMCritSection Protects a driver's handler from preemption 27H 39 VerifyAccess Verifies a process's right of access to memory 2AH 42 AttachDD Returns IDC entry point to a specified driver 2DH 45 AllocGDTSelector Allocates one or more GDT selectors 2EH 46 PhysToGDTSelector Maps a 32-bit physical address to a GDT selector 2FH 47 RealToProt Switches the processor from real mode to protected mode 30H 48 ProtToReal Switches the processor from protected mode into real mode 31H 49 EOI Issues an End-Of-Interrupt to the 8259 PIC 32H 50 UnPhysToVirt Notifies the kernel that virtual addresses are no longer needed 33H 51 TickCount Adds a timer handler to the system's list of timer handlers 34H 52 GetLIDEntry Obtains a logical ID for a device 35H 53 FreeLIDEntry Releases the logical ID for a device 36H 54 ABIOSCall Invokes an ABIOS service 37H 55 ABIOSCommonEntry Invokes an ABIOS common entry point 38H 56 RegisterStackUsage Indicates expected stack usage of device driver to interrupt manager ABIOSCall [K] [Int] [U] [Init] Invokes an ABIOS service on behalf of the requesting driver. Call with: AX Logical ID DH Subfunction 0 = start 1 = interrupt 2 = timeout DL 36H DS:SI Address of ABIOS request block Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: The ABIOS request block must be located in the driver's data segment. If the driver is in user mode, it should protect the execution of the ABIOS routine from interruption by first calling the DevHlp ROMCritSection. Related functions: ABIOSCommonEntry, FreeLIDEntry, GetLIDEntry, ROMCritSection. ABIOSCommonEntry [K] [Int] [U] [Init] Invokes an ABIOS common entry point on behalf of the requesting driver. Call with: DH Subfunction 0 = start 1 = interrupt 2 = timeout DL 37H DS:SI Address of ABIOS request block Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: The ABIOS request block must be located in the driver's data segment. If the driver is in user mode, it should protect the execution of the ABIOS routine from interruption by first calling the DevHlp ROMCritSection. Related functions: ABIOSCall, FreeLIDEntry, GetLIDEntry, ROMCritSection. AllocGDTSelector [Init] Allocates one or more GDT selectors for use by the device driver. Call with: CX Number of selectors requested DL 2DH ES:DI Address of array to receive selectors Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: The allocated selectors can be used at task time or at interrupt time. The DevHlp PhysToGDTSelector maps a particular physical memory address onto one of the allocated GDT selectors. The addressability remains valid until PhysToGDTSelector is called again with the same selector. Related functions: PhysToGDTSelector, PhysToUVirt. AllocPhys [K] [Init] Allocates a fixed block of memory. Call with: AX:BX Size in bytes DH Block position 0 = above 1 MB 1 = below 1 MB DL 18H Returns: If function successful Carry flag Clear AX:BX 32-bit physical address If function unsuccessful Carry flag Set AX Error code Notes: The DevHlp UnLock has no effect on memory allocated by this function. Avoid allocation of fixed memory below the 1 MB boundary; such allocations reduce the amount of memory available for real mode applications. Related functions: AllocReqPacket, FreePhys. AllocReqPacket [K] Allocates a block of memory large enough to contain the largest defined request packet and returns a bimodal pointer to the block. Call with: DH Wait/no-wait option 0 = wait for available request packet 1 = do not wait for request packet DL 0DH Returns: If function successful Carry flag Clear ES:BX Address of request packet If function unsuccessful Carry flag Set Notes: The memory blocks allocated with this function are a scarce system resource. Release these blocks as quickly as possible with the DevHlp FreeReqPacket. The state of the interrupt flag is not preserved. Related functions: AllocPhys, FreeReqPacket. AttachDD [K] [Init] [1.1] Returns the address of the interdevice driver communication (IDC) entry point for the specified driver. Call with: DL 2AH DS:BX Address of device name DS:DI Address of buffer to receive entry point information (see Notes) Returns: If function successful Carry flag Clear and buffer filled as described below If function unsuccessful Carry flag Set Notes: The device name must be 8 bytes long and must exactly match the name field in the header of the driver which is being attached. Because the name fields of block device driver headers are not necessarily unique, this function is ordinarily used only to attach to character device drivers. The supplied buffer must be at least 12 bytes long and is filled by the system as follows: Offset Length Description 00H 2 Real mode offset of IDC entry point 02H 2 Real mode segment of IDC entry point 04H 2 Real mode data segment for specified driver 06H 2 Protected mode offset of IDC entry point 08H 2 Protected mode selector of IDC entry point 0AH 2 Protected mode data selector for specified driver When a driver wants to call another driver, it must first check the current CPU mode and verify that a nonzero IDC entry point address was returned by AttachDD for that mode. It must then set the DS register appropriately on behalf of the destination driver and enter the driver via a far call. Block [K] [U] Suspends the requesting thread. The thread is awakened when the specified timeout interval elapses or when another thread calls the DevHlp Run with the same event ID. Call with: AX:BX Event ID DI:CX Timeout interval in milliseconds (-1 = wait indefinitely) DH Interruptible flag 0 = interruptible 1 = noninterruptible DL 04H Returns: If event wakeup Carry flag Clear If timeout wakeup Carry flag Set Zero flag Set AL 00H If unusual wakeup Carry flag Set Zero flag Clear AL <> 00H Notes: Always call Block with DH = 0 unless the sleep will be for less than 1 second. Disable interrupts before calling Block; in this way, you avoid deadlocks or race conditions with the thread which will call Run. When the thread resumes execution, interrupts are already enabled. The event ID is an arbitrary 32-bit value. Take care to use a value that is not likely to be duplicated by other drivers because Run awakens all threads that have called Block with the same event ID. Related functions: DevDone, Run, SemRequest. DeRegister [K] Removes all monitors associated with the specified process from a monitor chain. Call with: AX Monitor chain handle (from MonitorCreate) BX Process ID DL 21H Returns: If function successful Carry flag Clear AX Number of monitors remaining in chain If function unsuccessful Carry flag Set AX Error code Note: Call this function only in protected mode. Related functions: MonitorCreate, Register. DevDone [K] [Int] Sets the Done bit in the status word of a request packet and then unblocks any threads waiting in the kernel for the completion of the request. Call with: DL 01H ES:BX Address of request packet Returns: Nothing Notes: The driver should store any necessary information, such as error flags, in the request packet before calling DevDone. Do not call DevDone with the address of a request packet that was allocated with AllocReqPacket. If a request is completed by the strategy routine at task time, the driver need not call DevDoneit can set the Done bit in the request packet status word and return. Related functions: Block, EOI, Run. EOI [Int] [Init] Issues an End-Of-Interrupt (EOI) to the 8259 programmable interrupt controller(s) as appropriate for the interrupt level. Call with: AL IRQ number (00H0FH) DL 31H Returns: Nothing Notes: If the specified interrupt level is for the slave 8259, then an EOI is issued to both the master and slave 8259. The state of the interrupt flag is not changed. To minimize the danger of excessive nested interrupts, make the DevHlp EOI call as late as possible in interrupt processing, disable interrupts immediately before the call, and leave them disabled until the IRET. Related functions: SetIRQ, UnSetIRQ. FreeLIDEntry [K] [Init] Releases the logical ID (LID) for a device. Call with: AX LID DL 35H DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Note: A driver which is deinstalling should release any LID it obtained previously during its initialization. Related functions: ABIOSCall, ABIOSCommonEntry, GetLIDEntry. FreePhys [K] [Init] Releases memory allocated by a previous call to the DevHlp AllocPhys. Call with: AX:BX 32-bit physical address DL 19H Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Note: A driver which is deinstalling should release any memory it allocated during its initialization. Related functions: AllocPhys, FreeReqPacket. FreeReqPacket [K] Releases memory previously allocated by a call to the DevHlp AllocReqPacket. Call with: DL 0EH ES:BX Address of request packet Returns: Nothing Note: The state of the interrupt flag is not preserved. Related functions: AllocReqPacket, FreePhys. GetDOSVar [K] [Init] Returns a bimodal pointer to a kernel variable. Call with: AL Variable of interest 1 = global information segment (T, I) 2 = local information segment (T) 3 = reserved 4 = stand-alone dump facility (T, I) 5 = system reboot (T, I) 6 = MSATS facility (T, I) 7 = system yield flag (T) 8 = system time-critical yield flag (T) 9 = reserved 10 = reserved 11 = real mode code page ID (T) DL 24H T = address valid at task time I = address valid at interrupt time Returns: If function successful Carry flag Clear AX:BX Address of variable (see Note) If function unsuccessful Carry flag Set Note: The value placed in AL determines the nature of the variable returned by this function, as follows: Value in AL Description of Variable 1 WORD containing the selector for the global information segment 2 or 11 DWORD containing the full 32-bit virtual address of the corresponding structure 4, 5, or 6 DWORD containing the address of a kernel entry point 7 or 8 BYTE that is nonzero if a thread with the same or higher priority (AL = 7) or time-critical priority (AL = 8) is ready to execute. Related functions: Register, TCYield, Yield. GetLIDEntry [U] [K] [Init] Obtains a logical ID (LID) for a device. Call with: AL Device ID BL LID specifier 0 = return first unclaimed LID <>0 = return relative LID DH LID type 0 = unshared LID 1 = shareable LID (DMA, POS) DL 34H DS Driver's data segment Returns: If function successful Carry flag Clear AX LID If function unsuccessful Carry flag Set AX Error code Related functions: ABIOSCall, ABIOSCommonEntry, FreeLIDEntry. Lock [K] [Init] Locks a memory segment so that it will not be swapped or moved during an I/O operation. Call with: AX Selector/segment BH Duration 0 = short-term (2 seconds or less) 1 = long-term BL Wait/no-wait flag 0 = wait until segment available and locked 1 = do not wait if segment unavailable DL 13H Returns: If function successful Carry flag Clear AX:BX Lock handle If function unsuccessful Carry flag Set AX Undefined BX Undefined Notes: Addresses received by the driver in read or write request packets need not be locked before use. The driver should verify the requesting process's access to the segment by calling the DevHlp VerifyAccess before calling Lock, and it should not yield the CPU between the two calls. If a long-term lock is requested, the kernel can move the segment to the region reserved for fixed memory allocations before returning. Related functions: UnLock, VerifyAccess, VirtToPhys. MonFlush [K] Removes all data from a monitor chain. A special flush record is sent through the chain to notify all monitors to discard any data in their internal buffers. Call with: AX Monitor chain handle (from MonitorCreate) DL 23H Returns: If function successful Carry flag Clear AX 0000H If function unsuccessful Carry flag Set AX Error code Notes: Subsequent MonWrite calls fail (or block) until the flush record emerges from the monitor chain. This function can be called only in protected mode. The state of the interrupt flag is not preserved. Related functions: DeRegister, MonitorCreate, MonWrite, Register. MonitorCreate [K] [Init] Creates an empty monitor chain or destroys a monitor chain. Call with: AX 0 (to create new monitor chain) Monitor chain handle (to destroy monitor chain) DL 1FH ES:SI Address of monitor chain buffer DS:DI Address of notification routine Returns: If function successful Carry flag Clear AX Monitor chain handle (if called with AX = 0) If function unsuccessful Carry flag Set AX Error code Notes: This function can be called only in protected mode. The first word of the monitor chain buffer must contain the buffer length in bytes. The driver places data into the monitor chain by calling the DevHlp MonWrite. When data emerges from the monitor chain, the kernel's monitor dispatcher places the data in the driver's monitor chain buffer and calls the notification routine previously registered with MonitorCreate. The notification routine is entered in protected mode with the following: ES:SI Address of monitor chain buffer DS Selector for driver's data segment The length of the data record is placed in the first word of the monitor chain buffer. Related functions: DeRegister, MonFlush, MonWrite, Register. MonWrite [K] [Int] [U] Writes a data record into the monitor chain so that it can pass to the processes registered as device monitors. Call with: AX Monitor chain handle (from MonitorCreate) CX Length of data record (bytes) DH Wait/no-wait flag 0 = wait for write complete 1 = do not wait for write complete DL 22H DS:SI Address of data record (must be in driver data segment) Returns: If function successful Carry flag Clear AX 0000H If function unsuccessful Carry flag Set AX Error code Notes: The wait/no-wait flag must be 1 for calls to MonWrite at interrupt time. The data record must not be larger than the size of the driver's monitor chain buffer minus 2 bytes. A MonFlush in progress can cause the MonWrite to fail with a not-enough-memory error code. The state of the interrupt flag is not preserved. Related functions: DeRegister, MonitorCreate, MonFlush, Register. PhysToGDTSelector [K] [Int] [U] [Init] Maps a 32-bit physical address to a GDT selector. The selector must have been previously obtained during driver initialization with AllocGDTSelector. Call with: AX:BX 32-bit physical address CX Length in bytes (0 = 65,536) DL 2EH SI Selector Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: The addressability of the GDT selector remains unchanged until the next call to PhysToGDTSelector with the same selector. The selector is for private use by the driver only and cannot be used in DevHlp calls or passed to a process. If the driver is in real mode, it can call the DevHlp RealToProt to switch the CPU into protected mode in order to use the GDT selector. The driver must then restore the CPU mode by calling ProtToReal. Related functions: AllocGDTSelector, PhysToUVirt, PhysToVirt, ProtToReal, RealToProt, VirtToPhys. PhysToUVirt [K] [Init] Converts a 32-bit physical address to a virtual address. In protected mode, a selector is allocated from the current LDT and must then be freed by a call to PhysToUVirt. In real mode, the physical address is converted to a segment-offset pair if it is below the 1 MB boundary. Call with: AX:BX 32-bit physical address CX Length in bytes (0 = 65,536) DH Request type 0 = get executable selector 1 = get read/write data selector 2 = release selector DL 17H Returns: If function successful Carry flag Clear ES:BX Virtual address (if called with DH = 0 or 1) If function unsuccessful Carry flag Set AX Error code Notes: PhysToUVirt allows a driver to make a fixed memory area (such as a video adapter refresh buffer or the driver's data segment) addressable for a process. The selector obtained from PhysToUVirt is typically passed to the process in a Generic IOCtl data buffer. For request type 2, the selector to be freed is placed in register AX, and the contents of registers BX and CX are ignored. Do not use the virtual address returned by this function in system calls. Related functions: PhysToGDTSelector, PhysToVirt, VirtToPhys. PhysToVirt [K] [Int] [Init] Converts a 32-bit physical address to a virtual address. If the function is called in real mode, the results depend on the physical address, current CPU mode, and hardware configuration. Call with: AX:BX 32-bit physical address CX Length in bytes (0 = 65,536) DH Destination for result 0 = leave result in DS:SI 1 = leave result in ES:DI DL 15H Returns: If function successful Carry flag Clear Zero flag Mode switch indicator Flag clear if no CPU mode switch Flag set if CPU mode has changed DS:SI Virtual address (if called with DH = 0) ES:DI Virtual address (if called with DH = 1) If function unsuccessful Carry flag Set AX Error code Notes: The returned virtual address does not remain valid if the driver blocks, yields, or is interrupted. Do not call DevHlp services other than PhysToVirt while the virtual address is needed. On PC/AT-compatible machines, if the CPU is in real mode and the physical address is above 1 MB, no mode switch is performed; instead, the destination segment register is loaded with a special value that provides addressability to the memory, and the function returns with interrupt disabled and the Zero flag clear. The addressability will be lost if the segment register is reloaded. On PS/2-compatible machines, if the CPU is in real mode and the physical address is above 1 MB, the CPU is switched to protected mode, and the function returns with the Zero flag set. If the destination for the virtual address is DS:SI, and no mode switch occurs, ES is preserved. If the CPU is switched to protected mode, and ES points to the driver's data segment, the segment address is replaced with the corresponding selector; otherwise, ES is set to zero. If the destination for the virtual address is ES:DI, and no mode switch occurs, DS is preserved. If the CPU is switched to protected mode, and DS points to the driver's data segment, the segment address is replaced with the corresponding selector; otherwise, DS is set to zero. If the Z flag indicates that a mode switch has occurred, the driver must recalculate virtual addresses previously obtained with this function. The driver should therefore first convert the address most likely to cause a mode switch. The driver must call UnPhysToVirt before it issues a call to DevHlp EOI or returns to the kernel to release the virtual address or addresses. Only one call to UnPhysToVirt is needed, regardless of the number of preceding PhysToVirt calls. UnPhysToVirt will also restore the previous CPU mode if necessary. Related functions: PhysToGDTSelector, PhysToUVirt, UnPhysToVirt, VirtToPhys. ProtToReal [K] [Int] Switches the processor from protected mode into real mode. Call with: DL 30H DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful (mode was not changed) Carry flag Set Notes: If the CPU mode is changed, the contents of the CS, DS, and SS registers are reset appropriately. The contents of ES are not preserved. The CPU mode at entry to the driver must always be restored before the driver returns. The driver can use the SMSW instruction to obtain the machine status word and determine the current mode. The PE bit (0) is set if the processor is in protected mode. Related functions: PhysToGDTSelector, RealToProt. PullParticular [K] [Int] Removes the specified request packet from the driver's request packet queue. Call with: DL 0BH DS:SI Address of request queue head ES:BX Address of request packet Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Related functions: PullReqPacket, PushReqPacket, SortReqPacket. PullReqPacket [K] [Int] Removes the next waiting request packet from the driver's request packet queue and returns a pointer to the packet. Call with: DL 0AH DS:SI Address of request queue head Returns: If function successful Carry flag Clear ES:BX Address of request packet If function unsuccessful Carry flag Set Related functions: PullParticular, PushReqPacket, SortReqPacket. PushReqPacket [K] Adds a request packet to the driver's request packet queue. Call with: DL 09H DS:SI Address of request queue head ES:BX Address of request packet Returns: Nothing Note: The request queue head is a DWORD which you should initialize to zero before the first call to SortReqPacket or PushReqPacket. Related functions: AllocReqPacket, PullParticular, PullReqPacket, SortReqPacket. QueueFlush [K] [Int] [U] Resets the pointers for the specified character queue, discarding any data in the queue. Call with: DL 10H DS:BX Address of character queue structure Returns: Nothing Related functions: QueueInit, QueueRead, QueueWrite. QueueInit [K] [Int] [U] [Init] Initializes a character queue for subsequent use by QueueRead, QueueWrite, and QueueFlush. Character queues are first in, first out and are implemented as simple ring buffers. Call with: DL 0FH DS:BX Address of character queue structure Returns: Nothing Note: A character queue has the following structure: Offset Length Description 00H 2 Size of queue buffer (n) 02H 2 Buffer out index 04H 2 Number of characters in queue 06H n Buffer for character queue Before calling QueueInit, the driver must initialize the first word in the structure with the length of the buffer area. Related functions: QueueFlush, QueueRead, QueueWrite. QueueRead [K] [Int] [U] Returns and removes the oldest waiting character from a character queue. If the queue is empty, the function returns an error. Call with: DL 12H DS:BX Address of character queue structure Returns: If function successful Carry flag Clear AL Character If function unsuccessful (queue is empty) Carry flag Set Related functions: QueueFlush, QueueInit, QueueWrite. QueueWrite [K] [Int] [U] Adds a character to a character queue. If the queue is full, the function returns an error. Call with: AL Character DL 11H DS:BX Address of character queue structure Returns: If function successful Carry flag Clear If function unsuccessful (queue is full) Carry flag Set Related functions: QueueFlush, QueueInit, QueueRead. RealToProt [K] [Int] Switches the processor from real mode to protected mode. Call with: DL 2FH DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful (mode was not changed) Carry flag Set Notes: If the CPU mode is changed, the contents of the CS, DS, and SS registers are reset appropriately. The contents of ES are not preserved. The CPU mode at entry to the driver must always be restored before the driver returns. The driver can use the SMSW instruction to obtain the machine status word and determine the current mode. The PE bit (0) is clear if the processor is in real mode. Related functions: PhysToGDTSelector, ProtToReal. Register [K] Adds a device monitor to a monitor chain. Call with: AX Monitor handle (from MonitorCreate) CX Process ID of device monitor DH Placement flag 0 = no preference 1 = head of monitor chain 2 = tail of monitor chain DL 20H ES:SI Address of process's monitor input buffer ES:DI Address of process's monitor output buffer Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: This function can be called only in protected mode. When a process calls DosMonReg, the kernel passes the addresses of the monitor input and output buffers and the placement flag to the driver in a DosDevIOCtl Category 10 Function 40H request packet. The driver must obtain the PID of the process from the local information segment. A single process can register more than one monitor. The first word of the monitor input and output buffers must contain the length of the buffer (including the length word itself). The buffers must be at least 20 bytes longer than the size of the driver's monitor data packet. Related functions: DeRegister, GetDOSVar, MonFlush, MonitorCreate, MonWrite. RegisterStackUsage [Init] [1.1] Notifies the kernel's interrupt dispatcher of the driver's interrupt-time stack requirements. Call with: DL 38H DS:BX Address of stack information structure (see Notes) Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Notes: The supplied structure must be 14 bytes long and formatted as follows: Offset Length Description 00H 2 Contains the value 14 (the total length of the structure in bytes) 02H 2 Interrupt flags Bit(s) Significance 0 0 = driver's interrupt handler does not enable interrupts 1 = driver's interrupt handler enables interrupts 115 Reserved 04H 2 IRQ level used by driver 06H 2 Bytes of stack needed by driver's interrupt handler while interrupts are disabled 08H 2 Bytes of stack needed by driver's interrupt handler while interrupts are enabled 0AH 2 Bytes of stack needed by driver's interrupt handler after call to DevHlp EOI 0CH 2 Maximum number of levels of nested interrupts expected by driver If the specified number of nested interrupts is exceeded, the kernel's interrupt dispatcher disables that IRQ level until the system is restarted. A driver that handles multiple interrupt levels must make a separate call to RegisterStackUsage for each IRQ level. This function can be called only during driver initialization. If the function returns an error, the driver is expected to release any memory and IRQ levels it has allocated and abort its installation. ResetTimer [K] [Int] [Init] Notifies the kernel that the driver's timer handler should no longer be called on timer tick interrupts. Call with: DL 1EH CS:AX Address of timer handler DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Related functions: SetTimer, TickCount. ROMCritSection [U] Protects a driver's handler for real mode software interrupts from preemption by disabling session switching. Call with: AL Critical section flag 0 = exit critical section <>0 = enter critical section DL 26H Returns: Nothing Related functions: ABIOSCall, ABIOSCommonEntry, SetROMVector. Run [K] [Int] [U] Awakens all previously blocked threads that have the specified event identifier. Call with: AX:BX Event ID DL 05H Returns: If no threads awakened Zero flag Set AX 0000H If one or more threads awakened Zero flag Clear AX Number of threads awakened Related functions: Block, DevDone, SemClear. SchedClockAddr [K] [Init] Returns a bimodal pointer to the kernel's clock tick entry point. SchedClockAddr is called by the real time clock device driver during its initialization. The driver's clock tick interrupt handler calls the address returned by SchedClockAddr to distribute clock ticks throughout the system. Call with: DL 00H DS:AX Address of DWORD variable Returns: Bimodal pointer to kernel entry point placed in variable. Note: The driver must call the kernel clock tick entry point twice for each clock tick interrupt: once prior to calling the DevHlp EOI, and once afterwards. The parameters for the call are the following: AL Milliseconds since last call DH EOI indicator 0 = prior to EOI 1 = after EOI Related functions: EOI, SetIRQ. SemClear [K] [Int] [U] Clears (releases) a RAM or system semaphore. Any threads that were blocking on the semaphore become eligible for execution. Call with: AX:BX Semaphore handle DL 07H Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: RAM semaphores that will be used at interrupt time must be located in the driver's data segment or in locked storage. System semaphores can be created or opened only by a process. The process passes the semaphore handle to the driver in a DosDevIOCtl parameter buffer, and the driver then calls the DevHlp SemHandle to convert the handle to a value that it can use at task time or at interrupt time. A device driver cannot access system semaphores in user mode. Related functions: SemHandle, SemRequest. SemHandle [K] [Int] Converts a system semaphore handle obtained by a process into a handle that the driver can use at task time or interrupt time, or releases a handle previously obtained with this function. Call with: AX:BX Semaphore handle DH Action 0 = releasing handle 1 = obtaining handle DL 08H Returns: If function successful Carry flag Clear AX:BX New handle for semaphore If function unsuccessful Carry flag Set AX Error code Notes: If SemHandle is called with the handle of a RAM semaphore, it returns the same handle. The state of the interrupt flag is not preserved. Related functions: SemClear, SemRequest. SemRequest [K] [U] Claims a system or RAM semaphore. If the semaphore is already owned, the requesting thread is blocked until the semaphore becomes available or a timeout occurs. Call with: AX:BX Semaphore handle DI:CX Timeout in milliseconds 0 = do not wait if semaphore already owned -1 = wait indefinitely DL 06H Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set AX Error code Notes: RAM semaphores that will be used at interrupt time must be located in the driver's data segment or in locked storage. System semaphores can only be created or opened by a process. The process passes the semaphore handle to the driver in a DosDevIOCtl parameter buffer, and the driver then calls the DevHlp to convert the handle to a value that it can use at task time or at interrupt time. System semaphores may not be accessed by a device driver in user mode. The state of the interrupt flag is not preserved. Related functions: SemClear, SemHandle. SendEvent [K] [Int] Called by the keyboard device driver to indicate the occurrence of an event of interest to the Task Manager or signal handlers. Call with: AH Event type 0 = Reserved 1 = Ctrl-Break 2 = Ctrl-C 3 = Ctrl-NumLock 4 = Ctrl-PrtSc 5 = Shift-PrtSc 6 = Session-switch hot key BX Parameter (see Note) DL 25H Returns: If function successful Carry flag Clear If function unsuccessful (signal not sent) Carry flag Set Note: The parameters corresponding to the possible event types are as follows: Event Argument 0 Reserved (0) 1 Reserved (0) 2 Reserved (0) 3 Foreground session number 4 Reserved (0) 5 Reserved (0) 6 Hot key ID (see DosDevIOCtl Category 4 Function 56H) SetIRQ [K] [Init] Captures the hardware interrupt vector for the specified IRQ level. Call with: BX IRQ number (00H0FH) DH Sharing flag 0 = interrupt not shareable 1 = interrupt shareable DL 1BH DS Driver's data segment CS:AX Address of interrupt handler Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Note: The SetIRQ call fails if the IRQ level is already owned by another driver that specified it not shareable, is owned by a real mode interrupt handler, or is the IRQ used to cascade the slave 8259 interrupt controller (IRQ 2). Related functions: EOI, UnSetIRQ. SetROMVector [K] [Init] Replaces a real mode software interrupt handler with the driver's handler, returning the address of the previous handler. Call with: BX Interrupt number (00HFFH) DL 1AH CS:AX Address of interrupt handler CS:SI Address of WORD variable to receive real mode DS Returns: If function successful Carry flag Clear AX:DX Real mode pointer to previous handler Real mode DS for driver placed in variable. If function unsuccessful Carry flag Set Notes: Interrupt numbers 08H0FH, 50H57H, and 70H77H, which are reserved for hardware interrupts, cannot be used with this function. The driver's interrupt handler receives control in user mode, and the contents of all registers except for CS:IP are unpredictable. The driver must load the DS register with the value returned by the SetROMVector call so that it can address its own data segment. Related functions: ROMCritSection, SetIRQ. SetTimer [K] [Init] Adds a timer handler to the system's list of timer handlers to be called on each timer tick. Call with: DL 1DH DS Driver's data segment CS:AX Address of timer handler Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Notes: You can register a maximum of 32 driver timer handlers. A driver timer handler is entered with a far call, exits with a far return, and must preserve all registers. Related functions: ResetTimer, TickCount. SortReqPacket [K] Inserts a read or write request packet into a request packet queue, sorting the packets in order of starting sector number. Call with: DL 0CH DS:SI Address of request queue head ES:BX Address of request packet Returns: Nothing Note: The request queue head is a DWORD, which should be initialized to zero before the first call to SortReqPacket or PushReqPacket. Related functions: AllocReqPacket, PullParticular, PullReqPacket, PushReqPacket. TCYield [K] Yields the CPU so that any eligible threads with time-critical priority can execute. Call with: DL 03H Returns: Nothing Notes: TCYield is a subset of the DevHlp Yield; if Yield is called, an additional call to TCYield is unnecessary. The driver can check the system's time-critical yield flag to determine whether a call to TCYield is necessary. Make this check at maximum intervals of 3 milliseconds. The address of the flag is obtained with GetDOSVar. The state of the interrupt flag is not preserved. Related functions: Block, GetDOSVar, Run, Yield. TickCount [K] [Int] [U] [Init] Adds a timer handler to the system's list of timer handlers. The handler is called at the specified interval of 1 or more clock ticks. TickCount can also be called to change the interval for a handler previously registered with TickCount or SetTimer. Call with: BX Tick count between calls (0 = 65,536 ticks) DL 33H CS:AX Address of timer handler DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Notes: You can register a maximum of 32 driver timer handlers. A driver timer handler is entered with a far call, exits with a far return, and must preserve all registers. The driver cannot call this function in user mode or interrupt mode to register a new handler. Related functions: ResetTimer, SetTimer. Unlock [K] [Init] Unlocks a memory segment that was previously marked nonmovable and nonswappable by a call to the DevHlp Lock. Call with: AX:BX Lock handle (from Lock) DL 14H Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Note: This function cannot be used on memory allocated with the DevHlp AllocPhys; such memory is permanently fixed. Related functions: Lock, VirtToPhys. UnPhysToVirt [K] [Int] [Init] Notifies the kernel that the virtual addresses obtained with previous PhysToVirt calls are no longer needed. If PhysToVirt changed the CPU mode, the original mode is restored. Call with: DL 32H Returns: If no mode change Zero flag Clear If mode was changed Zero flag Set Note: If a mode switch occurs, the CS and SS segment registers are reset appropriately. The DS and ES registers are initialized to point to the driver's data segment, regardless of their previous contents. Related function: PhysToVirt. UnSetIRQ [K] [Int] [Init] Releases ownership of a hardware interrupt. Call with: BX IRQ number (00H0FH) DL 1CH DS Driver's data segment Returns: If function successful Carry flag Clear If function unsuccessful Carry flag Set Related functions: EOI, SetIRQ. VerifyAccess [K] Verifies a process's right of access to a range of memory addresses. Call with: AX:DI Address of memory CX Length in bytes (0 = 65,536) DH Type of access 0 = read access 1 = read/write access DL 27H Returns: If function successful (access verified) Carry flag Clear If function unsuccessful (access not allowed) Carry flag Set Notes: A driver must verify addresses received from a process in a DosDevIOCtl call or by some other means prior to locking or accessing those addresses. Addresses passed to the driver in a read or write request packet have been previously locked and verified by the kernel and need not be verified by the driver. In real mode, a success flag is always returned. Related functions: Lock, Unlock, VirtToPhys. VirtToPhys [K] [Init] Converts a virtual address (segment:offset or selector:offset) to a 32-bit physical address. Call with: DL 16H DS:SI Virtual address Returns: If function successful Carry flag Clear AX:BX 32-bit physical address If function unsuccessful Carry flag Set Note: The virtual address should be locked before calling VirtToPhys unless the segment is known to be locked already. Related functions: Lock, PhysToUVirt, PhysToVirt, UnLock. Yield [K] Yields the CPU so that any eligible threads with the same or a higher priority can execute. Call with: DL 02H Returns: Nothing Notes: The driver can check the system's yield flag to determine whether a call to Yield is necessary. Make this check at maximum intervals of 3 milliseconds. The address of the flag is obtained with GetDOSVar. The state of the interrupt flag is not preserved. Related functions: Block, GetDOSVar, Run, TCYield. SECTION 7 APPENDIXES Appendix A OS/2 Error Codes Decimal Hex Meaning 0 0000H No error 1 0001H Invalid function number 2 0002H File not found 3 0003H Path not found 4 0004H Out of handles 5 0005H Access denied 6 0006H Invalid handle 7 0007H Memory control blocks destroyed 8 0008H Insufficient memory 9 0009H Invalid memory block address 10 000AH Invalid environment 11 000BH Invalid format 12 000CH Invalid access code 13 000DH Invalid data 14 000EH Unknown unit 15 000FH Invalid disk drive 16 0010H Cannot remove current directory 17 0011H Not same device 18 0012H No more files 19 0013H Disk write-protected 20 0014H Unknown unit 21 0015H Drive not ready 22 0016H Unknown command 23 0017H Data error (CRC) 24 0018H Bad request structure length 25 0019H Seek error 26 001AH Unknown type of medium 27 001BH Sector not found 28 001CH Printer out of paper 29 001DH Write fault 30 001EH Read fault 31 001FH General failure 32 0020H Sharing violation 33 0021H Lock violation 34 0022H Invalid disk change 35 0023H FCB unavailable 36 0024H Sharing buffer exceeded 3749 0025H0031H Reserved 50 0032H Unsupported network request 51 0033H Remote machine not listening 52 0034H Duplicate name on network 53 0035H Network name not found 54 0036H Network busy 55 0037H Device no longer exists on network 56 0038H NetBIOS command limit exceeded 57 0039H Error in network adapter hardware 58 003AH Incorrect response from network 59 003BH Unexpected network error 60 003CH Remote adapter incompatible 61 003DH Print queue full 62 003EH Insufficient memory for print file 63 003FH Print file canceled 64 0040H Network name deleted 65 0041H Network access denied 66 0042H Incorrect network device type 67 0043H Network name not found 68 0044H Network name limit exceeded 69 0045H NetBIOS session limit exceeded 70 0046H File sharing temporarily paused 71 0047H Network request not accepted 72 0048H Print or disk redirection paused 7379 0049H004FH Reserved 80 0050H File already exists 81 0051H Reserved 82 0052H Cannot make directory 83 0053H Fail on Int 24H (critical error) 84 0054H Too many redirections 85 0055H Duplicate redirection 86 0056H Invalid password 87 0057H Invalid parameter 88 0058H Network device fault 89 0059H No process slots available 90 005AH System error 91 005BH Timer service table overflow 92 005CH Timer service table duplicate 93 005DH No items to work on 94 005EH Reserved 95 005FH Interrupted system call 9699 0060H0063H Reserved 100 0064H Open semaphore limit exceeded 101 0065H Exclusive semaphore already owned 102 0066H DosCloseSem found semaphore set 103 0067H Too many exclusive semaphore requests 104 0068H Operation invalid at interrupt time 105 0069H Semaphore owner terminated 106 006AH Semaphore limit exceeded 107 006BH Insert drive B disk into drive A 108 006CH Drive locked by another process 109 006DH Write on pipe with no reader 110 006EH Open/create failed due to explicit fail command 111 006FH Buffer too small 112 0070H Disk is full 113 0071H No more search handles 114 0072H Invalid target handle for DosDupHandle 115 0073H Bad user virtual address 116 0074H Error on display write or keyboard read 117 0075H Invalid DosDevIOCtl category 118 0076H Invalid value for verify flag 119 0077H Driver does not support DosDevIOCtl 120 0078H Invalid function called 121 0079H Timed out waiting for semaphore 122 007AH Insufficient data in buffer 123 007BH Invalid character or bad filename 124 007CH Unimplemented information level 125 007DH No volume label found 126 007EH Invalid module handle 127 007FH Procedure not found in module 128 0080H No child processes found 129 0081H Child processes still running 130 0082H Invalid handle operation for direct disk access 131 0083H Cannot seek to negative offset 132 0084H Cannot seek on pipe or device 133 0085H Drive has previously joined drives 134 0086H Drive is already joined 135 0087H Drive is already substituted 136 0088H Drive is not joined 137 0089H Drive is not substituted 138 008AH Cannot join to joined drive 139 008BH Cannot substitute to substituted drive 140 008CH Cannot join to substituted drive 141 008DH Cannot substitute to joined drive 142 008EH Drive is busy 143 008FH Cannot join or substitute drive to directory on same drive 144 0090H Must be subdirectory of root 145 0091H Joined directory must be empty 146 0092H Path is already used in substitute 147 0093H Path is already used in join 148 0094H Path is being used by another process 149 0095H Cannot join or substitute drive having directory that is target of previous substitute 150 0096H System trace error 151 0097H DosMuxSemWait errors 152 0098H System limit on DosMuxSemWait calls exceeded 153 0099H Invalid list format 154 009AH Volume label too big 155 009BH Cannot create another TCB 156 009CH Signal refused 157 009DH Segment is discarded 158 009EH Segment was not locked 159 009FH Bad thread ID address 160 00A0H Bad environment pointer 161 00A1H Bad pathname for DosExecPgm 162 00A2H Signal already pending 163 00A3H Unknown medium 164 00A4H No more threads available 165 00A5H Monitors not supported 166179 00A6H00B3H Reserved 180 00B4H Invalid segment number 181 00B5H Invalid call gate 182 00B6H Invalid ordinal 183 00B7H Shared segment or system semaphore already exists 184 00B8H No child process running 185 00B9H Child process is still alive 186 00BAH Invalid flag number 187 00BBH Semaphore does not exist 188 00BCH Invalid starting code segment 189 00BDH Invalid stack segment 190 00BEH Invalid module type 191 00BFH Wrong EXE file header 192 00C0H Invalid EXE file, LINK errors 193 00C1H Invalid EXE format 194 00C2H Iterated data exceeds 64 KB 195 00C3H Invalid minimum allocation size 196 00C4H Invalid dynamic link from ring 2 segment 197 00C5H IOPL not enabled in CONFIG.SYS 198 00C6H Invalid segment descriptor privilege level 199 00C7H Automatic data segment exceeds 64 KB 200 00C8H Ring 2 segment must be movable 201 00C9H Relocation chain exceeds segment limit 202 00CAH Infinite loop in relocation chain 203 00CBH Environment variable not found 204 00CCH Not current country 205 00CDH No process with handler to receive signal 206 00CEH Filename or extension too long 207 00CFH Ring 2 stack in use 208 00D0H Meta expansion too long 209 00D1H Invalid signal number 210 00D2H Inactive thread 211 00D3H File system information not available 212 00D4H Locked error 213 00D5H Bad dynamic link 214 00D6H Too many modules 215 00D7H Nesting not allowed 216 00D8H Cannot shrink ring 2 stack 217229 00D9H00E5H Reserved 230 00E6H Nonexistent pipe or invalid operation 231 00E7H Specified pipe is busy 232 00E8H No data on nonblocking pipe read 233 00E9H Pipe disconnected by server 234 00EAH Additional data is available 235239 00EBH00EFH Reserved 240 00F0H Network session was canceled 241261 00F1H0105H Reserved 262 0106H Stack too large 263302 0107H012EH Reserved 303 012FH Invalid process ID 304 0130H Invalid priority level increment or decrement 305 0131H Not a descendant process 306 0132H Requestor not Task Manager 307 0133H Invalid priority class 308 0134H Invalid scope 309 0135H Invalid thread ID 310 0136H Cannot shrink DosSubSet segment 311 0137H Out of memory (DosSubAlloc) 312 0138H Invalid block specified (DosSubFree) 313 0139H Bad size parameter (DosSubAlloc or DosSubFree) 314 013AH Bad flag parameter (DosSubSet) 315 013BH Invalid segment selector 316 013CH Message too long for buffer 317 013DH Message ID number not found 318 013EH Unable to access message file 319 013FH Invalid message file format 320 0140H Invalid insertion variable count 321 0141H Unable to perform function 322 0142H Unable to wake up 323 0143H Invalid system semaphore handle 324 0144H No timers available 325 0145H Reserved 326 0146H Invalid timer handle 327 0147H Date or time invalid 328 0148H Internal system error 329 0149H Current queue name does not exist 330 014AH Current process is not queue owner 331 014BH Current process owns queue 332 014CH Duplicate queue name 333 014DH Queue record does not exist 334 014EH Inadequate queue memory 335 014FH Invalid queue name 336 0150H Invalid queue priority parameter 337 0151H Invalid queue handle 338 0152H Queue link not found 339 0153H Queue memory error 340 0154H Previous queue record was at end of queue 341 0155H Process does not have access to queues 342 0156H Queue is empty 343 0157H Queue name does not exist 344 0158H Queues not initialized 345 0159H Unable to access queues 346 015AH Unable to add new queue 347 015BH Unable to initialize queues 348 015CH Reserved 349 015DH Invalid Vio function replaced 350 015EH Invalid pointer to parameter 351354 015FH0162H Reserved 355 0163H Unsupported screen mode 356 0164H Invalid cursor width value 357 0165H Reserved 358 0166H Invalid row value 359 0167H Invalid column value 360365 0168H016DH Reserved 366 016EH Invalid wait flag setting 367 016FH Screen not previously locked 368 0170H Reserved 369 0171H Invalid session ID 370 0172H No sessions available 371 0173H Session not found 372 0174H Title cannot be changed 373 0175H Invalid parameter (Kbd) 374 0176H Reserved 375 0177H Invalid wait parameter 376 0178H Invalid length for keyboard 377 0179H Invalid echo mode mask 378 017AH Invalid input mode mask 379 017BH Invalid monitor parameters 380 017CH Invalid device name string 381 017DH Invalid device handle 382 017EH Buffer too small 383 017FH Buffer empty 384 0180H Data record too large 385 0181H Reserved 386 0182H Mouse handle invalid or closed 387388 0183H0184H Reserved 389 0185H Invalid display mode parameters 390 0186H Reserved 391 0187H Invalid entry point 392 0188H Invalid function mask 393 0189H Reserved 394 018AH Pointer drawn 395 018BH Invalid frequency for DosBeep 396 018CH Cannot find COUNTRY.SYS file 397 018DH Cannot open COUNTRY.SYS file 398 018EH Country code not found 399 018FH Information truncated to fit buffer 400 0190H Selected type does not exist 401 0191H Selected type not in file 402 0192H Vio function for Task Manager only 403 0193H Invalid string length (Vio) 404 0194H VioDeRegister not allowed 405 0195H Popup screen not allocated 406 0196H Pop-up already on screen 407 0197H Kbd function for Task Manager only 408 0198H Invalid ASCIIZ string length (Kbd) 409 0199H Invalid function replacement mask 410 019AH KbdRegister not allowed 411 019BH KbdDeRegister not allowed 412 019CH Mou function for Task Manager only 413 019DH Invalid ASCIIZ string length (Mou) 414 019EH Invalid replacement mask 415 019FH MouRegister not allowed 416 01A0H MouDeRegister not allowed 417 01A1H Invalid action specified 418 01A2H INIT called more than once 419 01A3H Screen group number not found 420 01A4H Caller is not shell 421 01A5H Invalid parameters (Vio) 422 01A6H Save/restore already owned 423 01A7H Thread unblocked by VioModeUndo or VioSavRedrawUndo 424 01A8H Reserved 425 01A9H Caller not Task Manager 426 01AAH VioRegister not allowed 427 01ABH No VioModeWait thread exists 428 01ACH No VioSavRedrawWait thread exists 429 01ADH Function invalid in background 430 01AEH Function not allowed during pop-up 431 01AFH Caller is not the base shell 432 01B0H Invalid status requested 433 01B1H No-wait parameter out of bounds 434 01B2H Cannot lock screen 435 01B3H Invalid wait parameter 436 01B4H Invalid Vio handle 437 01B5H Reserved 438 01B6H Invalid length for Vio function 439 01B7H Invalid Kbd handle 440 01B8H Out of Kbd handles 441 01B9H Cannot create logical keyboard 442 01BAH Code page load failed 443 01BBH Invalid code page ID 444 01BCH No code page support 445 01BDH Keyboard focus required 446 01BEH Caller already has focus 447 01BFH Keyboard subsystem is busy 448 01C0H Invalid code page 449 01C1H Cannot get keyboard focus 450 01C2H Session is not selectable 451 01C3H Parent/child session not in foreground 452 01C4H Not parent of specified child 453 01C5H Invalid session start mode 454 01C6H Invalid session start option 455 01C7H Invalid session bonding option 456 01C8H Invalid session select option 457 01C9H Session started in background 458 01CAH Invalid session stop option 459 01CBH Reserved parameters not 0 460 01CCH Session parent process already exists 461 01CDH Invalid data length 462 01CEH Parent session not bound 463 01CFH Retry request block allocation 464 01D0H Unavailable for detached process (Kbd) 465 01D1H Unavailable for detached process (Vio) 466 01D2H Unavailable for detached process (Mou) 467 01D3H No font available to support mode 468 01D4H User font active 469 01D5H Invalid code page specified 470 01D6H System displays do not support code page 471 01D7H Current display does not support code page 472 01D8H Invalid code page 473 01D9H Code page list is too small 474 01DAH Code page not moved 475 01DBH Mode switch initialization error 476 01DCH Code page not found 477 01DDH Internal error 478 01DEH Invalid session start trace indicator 479 01DFH Vio internal resource error 480 01E0H Vio shell initialization error 481 01E1H No Task Manager hard errors 482 01E2H DosSetCp unable to set display or keyboard code page 483 01E3H Error during Vio pop-up 484 01E4H Critical section overflow 485 01E5H Critical section underflow 486 01E6H Reserved parameter is not 0 487 01E7H Bad physical address 488 01E8H No selectors requested 489 01E9H Not enough GDT selectors available 490 01EAH Not a GDT selector 491 01EBH Invalid program type 492 01ECH Invalid program control 493 01EDH Invalid program inheritance option 494 01EEH Vio function not allowed in PM window 495 01EFH Function not supported in non-PM screen group 496 01F0H Vio shield already owned 497 01F1H Vio handles exhausted 498 01F2H Vio error occurred, details sent to error log 499 01F3H Invalid display context 500 01F4H Kbd input not available 501 01F5H Mou input not available 502 01F6H Invalid mouse handle 503 01F7H Invalid debugging parameters 504 01F8H Kbd function not allowed in PM window 505 01F9H Mou function not allowed in PM window 506 01FAH Invalid icon file Appendix B ASCII and IBM Extended Character Sets Number Char Dec Hex Control 0 00H NUL (Null)  1 01H SOH (Start of heading)  2 02H STX (Start of text)  3 03H ETX (End of text)  4 04H EOT (End of transmission)  5 05H ENQ (Enquiry)  6 06H ACK (Acknowledge)  7 07H BEL (Bell)  8 08H BS (Backspace) 9 09H HT (Horizontal tab) 10 0AH LF (Linefeed) 11 0BH VT (Vertical tab) 12 0CH FF (Formfeed) 13 0DH CR (Carriage return)  14 EH SO (Shift out) 15 0FH SI (Shift in)  16 10H DLE (Data link escape)  17 11H DC1 (Device control 1)  18 12H DC2 (Device control 2)  19 13H DC3 (Device control 3)  20 14H DC4 (Device control 4)  21 15H NAK (Negative acknowledge)  22 16H SYN (Synchronous idle)  23 17H ETB (End transmission block)  24 18H CAN (Cancel)  25 19H EM (End of medium) 26 1AH SUB (Substitute)  27 1BH ESC (Escape)  28 1CH FS (File separator)  29 1DH GS (Group separator)  30 1EH RS (Record separator)  31 1FH US (Unit separator) 32 20H ! 33 21H " 34 22H # 35 23H $ 36 24H % 37 25H & 38 26H ' 39 27H ( 40 28H ) 41 29H * 42 2AH + 43 2BH , 44 2CH - 45 2DH . 46 2EH / 47 2FH 0 48 30H 1 49 31H 2 50 32H 3 51 33H 4 52 34H 5 53 35H 6 54 36H 7 55 37H 8 56 38H 9 57 39H : 58 3AH ; 59 3BH < 60 3CH = 61 3DH > 62 3EH ? 63 3FH @ 64 40H A 65 41H B 66 42H C 67 43H D 68 44H E 69 45H F 70 46H G 71 47H H 72 48H I 73 49H J 74 4AH K 75 4BH L 76 4CH M 77 4DH N 78 4EH O 79 4FH P 80 50H Q 81 51H R 82 52H S 83 53H T 84 54H U 85 55H V 86 56H W 87 57H X 88 58H Y 89 59H Z 90 5AH [ 91 5BH \ 92 5CH ] 93 5DH ^ 94 5EH _ 95 5FH g 96 60H a 97 61H b 98 62H c 99 63H d 100 64H e 101 65H f 102 66H g 103 67H h 104 68H i 105 69H j 106 6AH k 107 6BH l 108 6CH m 109 6DH n 110 6EH o 111 6FH p 112 70H q 113 71H r 114 72H s 115 73H t 116 74H u 117 75H v 118 76H w 119 77H x 120 78H y 121 79H z 122 7AH { 123 7BH | 124 7CH } 125 7DH ~ 126 7EH 127 7FH DEL (Delete) 128 80H 129 81H 130 82H 131 83H 132 84H 133 85H 134 86H 135 87H 136 88H 137 89H 138 8AH 139 8BH 140 8CH 141 8DH 142 8EH 143 8FH 144 90H 145 91H 146 92H 147 93H 148 94H 149 95H 150 96H 151 97H 152 98H 153 99H 154 9AH 155 9BH 156 9CH 157 9DH 158 9EH 159 9FH 160 A0H 161 A1H 162 A2H 163 A3H 164 A4H 165 A5H 166 A6H 167 A7H 168 A8H 169 A9H 170 AAH 171 ABH 172 ACH 173 ADH 174 AEH 175 AFH 176 B0H 177 B1H 178 B2H 179 B3H 180 B4H 181 B5H 182 B6H 183 B7H 184 B8H 185 B9H 186 BAH 187 BBH 188 BCH 189 BDH 190 BEH 191 BFH 192 C0H 193 C1H 194 C2H 195 C3H 196 C4H 197 C5H 198 C6H 199 C7H 200 C8H 201 C9H 202 CAH 203 CBH 204 CCH 205 CDH 206 CEH 207 CFH 208 D0H 209 D1H 210 D2H 211 D3H 212 D4H 213 D5H 214 D6H 215 D7H 216 D8H 217 D9H 218 DAH 219 DBH 220 DCH 221 DDH 222 DEH 223 DFH 224 E0H 225 E1H 226 E2H 227 E3H 228 E4H 229 E5H 230 E6H 231 E7H 232 E8H 233 E9H 234 EAH 235 EBH 236 ECH 237 EDH 238 EEH 239 EFH 240 F0H 241 F1H 242 F2H 243 F3H 244 F4H 245 F5H 246 F6H 247 F7H 248 F8H 249 F9H 250 FAH 251 FBH 252 FCH 253 FDH 254 FEH 255 FFH Appendix C Resources Periodicals "Environments," biweekly column by Charles Petzold. PC Magazine. Ziff Davis Publishing, New York, N.Y. Microsoft Systems Journal. Microsoft Corp., Redmond, Wash. Assembly Language Assembly Language Primer for the IBM PC and XT. Robert Lafore. New York: New American Library, 1984. ISBN 0-452-25711-5. 8086/8088/80286 Assembly Language. Leo Scanlon. New York: Brady Communications Co., 1988. ISBN 0-13-246919-7. C Language Microsoft C Programming for the IBM. Robert Lafore. Indianapolis, Ind.: Howard K. Sams & Co., 1987. ISBN 0-672-22515-8. Proficient C. Augie Hansen. Redmond, Wash.: Microsoft Press, 1987. ISBN 1-55615-007-5. The C Programming Language, 2d ed. Brian Kernighan and Dennis Ritchie. Englewood Cliffs, N.J.: Prentice-Hall Inc., 1988. ISBN 0-13-110362-8. C: A Reference Manual, 2d ed. Samuel Harbison and Guy Steele. Englewood Cliffs, N.J.: Prentice-Hall Inc., 1987. ISBN 0-13-109802-0. Microsoft OS/2 Inside OS/2. Gordon Letwin. Redmond, Wash.: Microsoft Press, 1988. ISBN 1-55615-117-9. Programming the OS/2 Presentation Manager. Charles Petzold. Redmond, Wash.: Microsoft Press, 1989. ISBN 1-55615-170-5. IBM Operating System/2 Technical Reference. IBM Corp. IBM Technical Directory, P.O. Box 2009, Racine, Wis. 53404. Part no. 6280201. Operating System Fundamentals Operating Systems: Design and Implementation. Andrew S. Tanenbaum. Englewood Cliffs, N.J.: Prentice-Hall Inc., 1987. ISBN 0-13-637406-9. An Introduction to General Systems Thinking. Gerald Weinberg. New York: John Wiley and Sons, 1975. ISBN 0-471-92563-2. Operating System Concepts. James Peterson and Abraham Silberschatz. Reading, Mass.: Addison-Wesley Publishing Co., 1983. ISBN 0-201-06097-3. Intel 80286 and 80386 Inside the 80286. Edmund Strauss. New York: Brady Publishing (Prentice-Hall Inc.), 1986. ISBN 0-89303-582-3. The 80386 Book. Ross P. Nelson. Redmond, Wash.: Microsoft Press, 1988. ISBN 1-55615-138-1. Dr. Dobb's Toolbook of 80286/80386 Programming. Edited by Philip Robinson. Redwood City, Calif.: M&T Publishing Inc., 1988. ISBN 0-934375-42-9. iAPX 286 Programmer's Reference Manual. Intel Corp. Literature Dept., 3065 Bowers Ave., Santa Clara, Calif. 95051. Order no. 210498. iAPX 286 Operating Systems Writer's Guide. Intel Corp. Literature Dept., 3065 Bowers Ave., Santa Clara, Calif. 95051. Order no. 121960. 80386 Programmer's Reference Manual. Intel Corp. Literature Dept., 3065 Bowers Ave., Santa Clara, Calif. 95051. Order no. 230985. 80386 System Software Writer's Guide. Intel Corp. Literature Dept., 3065 Bowers Ave., Santa Clara, Calif. 95051. Order no. 231499. 80387 Programmer's Reference Manual. Intel Corp. Literature Dept., 3065 Bowers Ave., Santa Clara, Calif. 95051. Order no. 231917. PC, PC/AT, and PS/2 Architecture The IBM Personal Computer from the Inside Out, rev. ed. Murray Sargent and Richard L. Shoemaker. Reading, Mass.: Addison-Wesley Publishing Co., 1986. ISBN 0-201-06918-0. Programmer's Guide to PC and PS/2 Video Systems. Richard Wilton. Redmond, Wash.: Microsoft Press, 1987. ISBN 1-55615-103-9. Personal Computer AT Technical Reference. IBM Corp. IBM Technical Directory, P.O. Box 2009, Racine, Wis. 53404. Part no. 6280070. Options and Adapters Technical Reference. IBM Corp. IBM Technical Directory, P.O. Box 2009, Racine, Wis. 53404. Part no. 6322509. Personal System/2 Model 50/60/70/80 Technical Reference. IBM Corp. IBM Technical Directory, P.O. Box 2009, Racine, Wis. 53404. Part no. 68X2330. BIOS and ABIOS Interface Technical Reference. IBM Corp. IBM Technical Directory, P.O. Box 2009, Racine, Wis. 53404. Part no. 68X2341. Appendix D OS/2 Load Module Format OS/2 programs, dynlink libraries, and device drivers reside on the disk in load modules called segmented executable files, or "new EXE" files. The new EXE file format, which is also used by Microsoft Windows, is a superset of the "old EXE" format used by MS-DOS. Components of a Segmented Executable File A segmented EXE file is composed of many distinct but interrelated elements, as shown in Figure D-1 on the following page. A hex dump of a simple OS/2 program (the PORTS.EXE file from Chapter 14) is shown in Figure D-2, beginning on p. 717, with the various elements of the file marked. You can use the Microsoft utility program EXEHDR.EXE to display much of the information described in this section for any specific OS/2 program, library, or driver. Throughout the remainder of this appendix, the term counted string is used to refer to a string that consists of a length byte followed by the ASCII characters of the actual string. The value of the length byte does not include the length byte itself, and the string is not terminated by a null byte. Ŀ Old EXE header Ĵ MS-DOS stub program Ĵ New EXE header Ĵ Segment table Ĵ Resource table Ĵ Resident names table Ĵ Module reference table Ĵ Imported names table Ĵ Entry table Ĵ Nonresident names table Ĵ Segment #1 code or data Segment #1 relocation information Ĵ Ĵ Segment #n code or data Segment #n relocation information Ĵ Resources Figure D-1. Block diagram of a file containing an OS/2 program, dynlink library, or device driver. 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 4D 5A 5E 00 04 00 00 00 04 00 00 00 FF FF 00 00 MZ^............. Old EXEĴ 0010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@....... header 0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ................ 0040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ........!..L.!Th MS-DOSĴ 0050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno stub program 0060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS 0070 6D 6F 64 65 2E 0D 0A 24 00 00 00 00 00 00 00 00 mode...$........ 0080 4E 45 05 01 7D 00 12 00 19 6A 56 F8 0A 02 03 00 NE..}....jV..... New EXEĴ 0090 00 00 00 10 00 00 02 00 00 00 03 00 03 00 01 00 ................ header 00A0 0D 00 40 00 58 00 58 00 71 00 73 00 0F 01 00 00 ..@.X.X.q.s..... 00B0 02 00 09 00 00 00 01 00 00 00 00 00 00 00 00 00 ................ Segment 00C0 01 00 20 00 10 08 21 00 02 00 C1 00 00 0D C1 00 .. ...!......... table00D0 03 00 5E 00 01 0C 5E 00 ..^...^. Resident 05 50 4F 52 54 53 00 00 .PORTS.. namesĴ 00E0 05 52 50 4F 52 54 01 00 05 57 50 4F 52 54 02 00 .RPORT...WPORT.. table 00F0 00 . Module reference01 00 .. table Imported names00 08 44 4F 53 43 41 4C 4C 53 ..DOSCALLS table Entry 02 FF 09 ... table0100 CD 3F 01 00 00 11 CD 3F 01 0F 00 01 00 00 00 .?.....?....... Nonresident names 09 . table0110 50 4F 52 54 53 2E 45 58 45 00 00 00 PORTS.EXE... 00 00 00 00 .... 0120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ . Filler bytesĴ . . 01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Code segment 0200 55 8B EC 52 8B 56 06 EC 32 E4 5A 5D CA 02 00 55 U..R.V..2.Z]...U (IO_TEXT)0210 8B EC 50 52 8B 46 06 8B 56 08 EE 5A 58 5D CA 04 ..PR.F..V..ZX].. 0220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ . Filler bytesĴ . . 03F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0400 1E 07 6A 00 6A 00 6A 00 68 FF 00 9A FF FF 00 00 ..j.j.j.h....... . Code segment . (_TEXT)Ĵ . 04B0 AA 8A C4 E8 02 00 AA C3 04 30 3C 39 7E 02 04 07 .........0<9~... 04C0 C3 04 00 03 01 98 00 01 00 05 00 03 01 7A 00 01 .............z.. Relocation tableĴ 04D0 00 8A 00 03 01 8F 00 01 00 45 00 03 00 63 00 FF .........E...c.. for _TEXT 04E0 00 01 00 ... 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. 04F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ . Filler bytesĴ . . 05F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0600 00 00 0D 0A 44 6F 73 50 6F 72 74 41 63 63 65 73 ....DosPortAcces . Data segmentĴ . (_DATA) . 0650 20 20 46 0D 0A 4E 4E 4E 4E 20 20 20 4E 4E F..NNNN NN Figure D-2. Simple segmented executable file (the PORTS.EXE program from Chapter 14). Note that this program has no resource table nor any resources at the end of the file. Old EXE Header The format of the "old EXE" header is shown in Figure D-3. The most important parts of this header are the familiar MS-DOS signature ("MZ") for a relocatable executable file, the value 0040H at offset 0018H, which is the offset of the MS-DOS stub program and which also signals the existence of a "new EXE" header, and the 32-bit offset to the "new EXE" header at offset 003CH. 00H Ŀ Signature byte 1 `M' 01H Ĵ Signature byte 2 `Z' 02H Ĵ Length of file header and program image MOD 512 04H Ĵ Length of file header and program image in 512-byte pages 06H Ĵ Number of relocation table items for MS-DOS stub program 08H Ĵ Size of old EXE header in paragraphs 0AH Ĵ Minimum paragraphs of extra memory needed for execution of MS-DOS stub program 0CH Ĵ Maximum paragraphs of extra memory needed for execution of MS-DOS stub program 0EH Ĵ Segment displacement of stub program stack 10H Ĵ Contents of SP register at stub program entry 12H Ĵ Checksum 14H Ĵ Contents of IP register at stub program entry 16H Ĵ Segment displacement of stub program code 18H Ĵ File offset of first stub program relocation item (0040H indicates segmented file present) 1AH Ĵ Overlay number (0) 1CH Ĵ Reserved 24H Ĵ OEM identifier 26H Ĵ OEM information 28H Ĵ Reserved 3CH Ĵ File offset of new EXE header 40H Figure D-3. Old EXE header at the beginning of an OS/2 executable file. MS-DOS Stub Program The MS-DOS stub program, which is executed if the EXE file is loaded in real mode under MS-DOS or in DOS compatibility mode under OS/2, always begins at offset 0040H in the file. If you do not explicitly specify a stub program in the DEF file when you link the OS/2 program, the Linker inserts a program that displays an error message and terminates. The stub program has its own relocation table and can be of any size. The entry point, stack size, and the locations of the relocation table, code, and data for the stub program are specified in the old EXE header. When a program is "bound" for use as a Family application, the BIND utility replaces the original stub program with a "real mode loader" and an additional routine for each OS/2 API function that the program uses. When the bound program is run, the real mode loader gains control, performs all necessary relocations, and patches each API function call to point to a routine that translates the parameters on the stack into the appropriate parameters in registers and executes a software interrupt. New EXE Header The new EXE header is much more complicated than the old EXE header. The fields of the new EXE header are shown in Figure D-4. Some header fields are set by DEF file directives and describe the characteristics and behavior of a program or library, as well as the initial size of the stack and local heap. The remaining fields specify the locations and sizes of other file elements such as the resident and nonresident names tables and the segment table. (The actual code and data segments are described in the segment table rather than in the new EXE header.) A critical field in the new EXE header is the file alignment unit size at offset 32H. This size is expressed as a power of 2; for example, the value 9 represents a file unit size of 512 bytes. Each component of an executable file (except for resources) begins at an offset within the file that is a multiple of the alignment unit size, and many of the tables within the file express locations and sizes in multiples of this unit. 00H Ŀ Signature byte 1 `N' 01H Ĵ Signature byte 2 `E' 02H Ĵ Linker major version number 03H Ĵ Linker minor version number 04H Ĵ Offset of entry table (relative to start of new EXE header) 06H Ĵ Length of entry table 08H Ĵ 32-bit CRC of entire file 0CH Ĵ Module flags Bits Significance Bits Significance 0-1 DGROUP characteristics 8-10 Application type 0 = no DGROUP 0 = unknown 1 = single DGROUP (shared) 1 = full screen 2 = multiple DGROUPs (unshared) 2 = Vio application, 2 0 = global initialization can run in window (dynlink libraries) 3 = Presentation Manager 1 = instance initialization application 3 0 = real mode or protected mode 11 0 = not Family application 1 = protected mode only 1 = Family application 4 1 = contains 8086 instructions 12 Reserved only 13 0 = file is executable 5 1 = contains 80286 instructions 1 = invalid load module 6 1 = contains 80386 instructions (Link errors) 7 1 = contains floating-point 14 Reserved instructions 15 0 = application 1 = dynlink library or driver 0EH Ĵ Segment number of DGROUP 10H Ĵ Initial size of local heap 12H Ĵ Initial size of thread 1 stack 14H Ĵ Contents of IP register at program entry 16H Ĵ Segment number containing entry point 18H Ĵ Contents of SP register at program entry 1AH Ĵ Segment number containing thread 1 stack 1CH Ĵ Entries in segment table 1EH Ĵ Entries in module reference table 20H Ĵ Length of nonresident names table 22H ĴĿ Offset of segment table 24H Ĵ Offset of resource table 26H Ĵ Offset of resident names table Offsets relative to start of new EXE header 28H Ĵ Offset of module ref. table 2AH Ĵ Offset of imported names table 2CH Ĵ Offset of nonresident names table Offset relative to start of file 30H Ĵ Number of movable entry points 32H Ĵ Size of file alignment unit (power of 2) 34H Ĵ Number of resource table entries 36H Ĵ Target operating system Value Meaning 0 Unknown 1 OS/2 2 Windows 3 MS-DOS 4 37H Ĵ Reserved 40H Figure D-4. New EXE header, which follows the MS-DOScompatible stub program in an OS/2 executable file. Segment Table The segment table describes the size, location within the file, and characteristics of each of the program's code and data segments. This table is read into memory by the OS/2 loader; it remains resident as long as the program or library is in use so that the loader can quickly locate discardable or load-on-demand code and data segments within the file. Each entry in the table is 8 bytes long and has the following format: Offset Length Description 00H 2 Offset of beginning of segment within file (expressed as multiple of file unit size) 02H 2 Length of segment (0 = 65,536 bytes) 04H 2 Segment flags Bit(s) Significance 02 Segment type 0 = code 1 = data 3 0 = noniterated data 1 = iterated data 4 0 = fixed 1 = movable 5 0 = impure or nonshareable 1 = pure or shareable 6 0 = load-on-call 1 = preload 7 0 = execute/read if code, read/write if data 1 = execute-only if code, read-only if data 8 0 = no relocation table 1 = relocation table present 9 0 = nonconforming 1 = conforming 1011 Privilege level 12 0 = nondiscardable 1 = discardable 13 0 = 16-bit code segment 1 = 32-bit code segment 14 0 = normal segment 1 = huge segment 15 Reserved 06H 2 Minimum allocation size for segment (0 = 65,536 bytes) When other tables refer to segments, they use a one-based number that corresponds to an entry in the segment table. Resource Table The resource table is used in Presentation Manager applications to specify the size, location, and type of resources at the end of the file. The table starts with a 16-bit resource shift count: a power of 2 which defines the resource unit size. (Resources can use a different unit size than the one specified in the new EXE header for all other elements of the file.) The resource shift count is followed by two or more resource type headers, each header followed by resource entries that define the location, size, and characteristics of the associated resources. The end of the main portion of the resource table is indicated by a resource type header with a zero byte in the resource type position. Resource type headers have the following format: Offset Length Description 00H 2 Resource type identifier (if bit 15 = 1) or offset to type string (if bit 15 = 0) 02H 2 Number of resources for this type 04H 4 Reserved Each resource entry is formatted as follows: Offset Length Description 00H 2 Offset of resource within file (as multiple of resource unit size) 02H 2 Length of resource (as multiple of resource unit size) 04H 2 Resource flags Bit(s) Significance 03 Reserved 4 0 = fixed 1 = movable 5 0 = impure or nonshareable 1 = pure or shareable 6 0 = load-on-call 1 = preload 711 Reserved 1215 Discard priority 06H 2 Resource number (if bit 15 = 1) or offset to name string if bit 15 = 0) 08H 4 Reserved When resources and their types are identified with names rather than numbers, the names are stored as a series of counted strings at the end of the resource table (after the zero byte indicating that there are no more resource type headers). The offsets to a name or type string in a resource type header or resource entry are relative to the beginning of the resource table itself. The end of the block of strings is indicated by a single zero byte. Resident Names Table The resident names table lists all the entry points in the program or dynlink library (defined with EXPORTS statements in the DEF file) that were not assigned ordinal numbers. Each entry in the table is a counted string followed by a one-based index to the entry table. The end of the resident names table is indicated by an entry consisting of a single zero byte. The first entry in the resident names table is the module name and has an entry table index of zero. Module Reference Table The module reference table consists of a series of word-size (16-bit) entries. Each entry is the offset of a module name in the imported names table. The number of entries in the module reference tablethat is, the total number of modules from which functions are importedis found at offset 1EH in the new EXE header. Imported Names Table Each entry in the imported names table is a counted string that names an imported function or a module that contains imported functions. Imported functions are dynamically linked at load time. The offset of the beginning of each module name is given by the corresponding word in the module reference table. The offsets of function names are found within relocation records. Entry Table The entry table defines the segment, offset, and type of each entry point within the EXE file. The entry points are bundled together by segment, the number of bundles in the entry table varying from one bundle to many. Each bundle begins with two bytes. The first byte contains the number of entry point records in the bundle; the second byte contains FFH if the segment is movable or the segment number if the segment is fixed. When a bundle describes a movable segment (the usual case in an OS/2 application), the remainder of the bundle is one or more 6-byte entries in the following format: Offset Length Description 00H 1 Entry point flags Bit(s) Significance 0 0 = entry point not exported 1 = entry point exported 1 0 = entry point uses instance data 1 = entry point uses single data 2 Reserved 37 Number of stack parameter words 01H 2 Int 3FH instruction (CDH 3FH) 03H 1 Segment number of entry point 04H 2 Offset of entry point within segment The entries in bundles for fixed segments are 3 bytes long and have the following format: Offset Length Description 00H 1 Entry point flags (See above) 01H 2 Offset of entry point within segment When the loader must locate an entry point by its ordinal number, it first scans all the bundles in the entry table until it finds one with the appropriate segment number. It then multiplies the ordinal number by the appropriate size (3 or 6) and uses the result as an index into the bundle. Nonresident Names Table The nonresident names table lists all the entry points in the program or dynlink library (defined with EXPORTS statements in the DEF file) that were assigned explicit ordinal numbers. Each entry in the table is a counted string followed by a one-based index to the entry table. The end of the table is indicated by an entry consisting of a single zero byte. The first entry in the nonresident names table is either the description string specified in the DEF file or the filename if the DEF file contains no description string; the first entry has an entry table index of zero. Code and Data Segments The beginning of each code segment and data segment is aligned within the file as specified by the field at offset 32H in the new EXE header. A segment may be iterated data or noniterated data, as indicated by the field at offset 04H in the corresponding entry in the segment table. Iterated segments take the following form: Offset Length Description 00H 2 Number of iterations 02H 2 Bytes per iteration (n) 04H n Actual data for each iteration The size of a noniterated segment is specified by the field at offset 02H in the corresponding segment table entry. If the segment table indicates that a segment has relocation information, the segment's code or data is followed immediately by a 16-bit value for the number of relocation items and then by one or more 8-byte entries for each relocation item. The format for this 8-byte entry depends on the type of relocation item it representsthe three types found in normal applications are internal references, imported ordinals, and imported names. The format for an internal reference relocation entry, that is, a reference to another segment in the same executable file, has the following form: Offset Length Description 00H 1 Type of relocation 0 = 8-bit offset 2 = 16-bit segment 3 = 32-bit far pointer 5 = 16-bit offset 11 = 48-bit far pointer 13 = 32-bit offset 01H 1 0 or 4 (indicates internal reference) 02H 2 Offset of relocation item within segment 04H 1 FFH if movable segment or number of fixed segment 05H 1 Reserved (0) 06H 2 Ordinal of entry point if segment movable, otherwise offset of entry point within its segment The format for an imported ordinal relocation entry, that is, a reference to an entry point in another module which is being imported by ordinal number, has the following form: Offset Length Description 00H 1 Type of relocation (See above) 01H 1 1 or 5 (indicates imported ordinal) 02H 2 Offset of relocation item within segment 04H 2 One-based index to module reference table 06H 2 Function ordinal number The format for an imported name relocation entry, that is, a reference to an entry point in another module which is being imported by name, has the following form: Offset Length Description 00H 1 Type of relocation (See above) 01H 1 2 or 6 (indicates imported name) 02H 2 Offset of relocation item within segment 04H 2 One-based index to module reference table 06H 2 Offset to name of imported function in imported names table If bit 2 of the byte at offset 01H in a relocation entry is set, the address or value of the external reference is to be added to the contents of the address in the target segment. Otherwise, the address in the target segment contains the offset of the next location in the target segment that requires the same relocation; in other words, all the locations that should be replaced with the same address or value are chained together. The end of the chain is indicated by a -1. Resources Resources are static data blocks such as icons, cursors, menus, and bitmaps that are bound into the executable file for a Presentation Manager application with the Resource Compiler (RC.EXE). Each resource has a name and a type, which can be represented either as a binary 16-bit value or as an ASCII string. The combination of the name and type is unique for each resource in a particular executable file. A Kernel application can (but seldom does) contain resources. It can load and obtain a selector for a specific resource with the API function DosGetResource. The NEWEXE.H Header File The C header file NEWEXE.H (Figure D-5 on the following pages) defines the load module and is the ultimate recourse when you need information about the format. Using the #include directive, you can include this header in C programs that must access EXE or DLL files. /* * NEWEXE.H (C) Copyright Microsoft Corp 1984-1987 * * Data structure definitions for the OS/2 & Windows * executable file format. */ #define EMAGIC 0x5A4D /* Old magic number */ #define ENEWEXE sizeof(struct exe_hdr) /* Value of E_LFARLC for new .EXEs */ #define ENEWHDR 0x003C /* Offset in old hdr. of ptr. to new */ #define ERESWDS 0x0010 /* No. of reserved words (OLD) */ #define ERES1WDS 0x0004 /* No. of reserved words in e_res */ #define ERES2WDS 0x000A /* No. of reserved words in e_res2 */ #define ECP 0x0004 /* Offset in struct of E_CP */ #define ECBLP 0x0002 /* Offset in struct of E_CBLP */ #define EMINALLOC 0x000A /* Offset in struct of E_MINALLOC */ struct exe_hdr /* DOS 1, 2, 3 .EXE header */ { unsigned short e_magic; /* Magic number */ unsigned short e_cblp; /* Bytes on last page of file */ unsigned short e_cp; /* Pages in file */ unsigned short e_crlc; /* Relocations */ unsigned short e_cparhdr; /* Size of header in paragraphs */ unsigned short e_minalloc; /* Minimum extra paragraphs needed */ unsigned short e_maxalloc; /* Maximum extra paragraphs needed */ unsigned short e_ss; /* Initial (relative) SS value */ unsigned short e_sp; /* Initial SP value */ unsigned short e_csum; /* Checksum */ unsigned short e_ip; /* Initial IP value */ unsigned short e_cs; /* Initial (relative) CS value */ unsigned short e_lfarlc; /* File address of relocation table */ unsigned short e_ovno; /* Overlay number */ unsigned short e_res[ERES1WDS];/* Reserved words */ unsigned short e_oemid; /* OEM identifier (for e_oeminfo) */ unsigned short e_oeminfo; /* OEM information; e_oemid specific */ unsigned short e_res2[ERES2WDS];/* Reserved words */ long e_lfanew; /* File address of new exe header */ }; #define E_MAGIC(x) (x).e_magic #define E_CBLP(x) (x).e_cblp #define E_CP(x) (x).e_cp #define E_CRLC(x) (x).e_crlc #define E_CPARHDR(x) (x).e_cparhdr #define E_MINALLOC(x) (x).e_minalloc #define E_MAXALLOC(x) (x).e_maxalloc #define E_SS(x) (x).e_ss #define E_SP(x) (x).e_sp #define E_CSUM(x) (x).e_csum #define E_IP(x) (x).e_ip #define E_CS(x) (x).e_cs #define E_LFARLC(x) (x).e_lfarlc #define E_OVNO(x) (x).e_ovno #define E_RES(x) (x).e_res #define E_OEMID(x) (x).e_oemid #define E_OEMINFO(x) (x).e_oeminfo #define E_RES2(x) (x).e_res2 #define E_LFANEW(x) (x).e_lfanew #define NEMAGIC 0x454E /* New magic number */ #define NERESBYTES 9 /* Nine bytes reserved (now) */ #define NECRC 8 /* Offset into new header of NE_CRC */ struct new_exe /* New .EXE header */ { unsigned short ne_magic; /* Magic number NE_MAGIC */ unsigned char ne_ver; /* Version number */ unsigned char ne_rev; /* Revision number */ unsigned short ne_enttab; /* Offset of Entry Table */ unsigned short ne_cbenttab; /* Number of bytes in Entry Table */ long ne_crc; /* Checksum of whole file */ unsigned short ne_flags; /* Flag word */ unsigned short ne_autodata; /* Automatic data segment number */ unsigned short ne_heap; /* Initial heap allocation */ unsigned short ne_stack; /* Initial stack allocation */ long ne_csip; /* Initial CS:IP setting */ long ne_sssp; /* Initial SS:SP setting */ unsigned short ne_cseg; /* Count of file segments */ unsigned short ne_cmod; /* Entries in Module Reference Table */ unsigned short ne_cbnrestab; /* Size of non-resident name table */ unsigned short ne_segtab; /* Offset of Segment Table */ unsigned short ne_rsrctab; /* Offset of Resource Table */ unsigned short ne_restab; /* Offset of resident name table */ unsigned short ne_modtab; /* Offset of Module Reference Table */ unsigned short ne_imptab; /* Offset of Imported Names Table */ long ne_nrestab; /* Offset of Non-resident Names Table */ unsigned short ne_cmovent; /* Count of movable entries */ unsigned short ne_align; /* Segment alignment shift count */ unsigned short ne_cres; /* Count of resource entries */ unsigned char ne_exetyp; /* Target operating system */ char ne_res[NERESBYTES]; /* Pad structure to 64 bytes */ }; #define NE_MAGIC(x) (x).ne_magic #define NE_VER(x) (x).ne_ver #define NE_REV(x) (x).ne_rev #define NE_ENTTAB(x) (x).ne_enttab #define NE_CBENTTAB(x) (x).ne_cbenttab #define NE_CRC(x) (x).ne_crc #define NE_FLAGS(x) (x).ne_flags #define NE_AUTODATA(x) (x).ne_autodata #define NE_HEAP(x) (x).ne_heap #define NE_STACK(x) (x).ne_stack #define NE_CSIP(x) (x).ne_csip #define NE_SSSP(x) (x).ne_sssp #define NE_CSEG(x) (x).ne_cseg #define NE_CMOD(x) (x).ne_cmod #define NE_CBNRESTAB(x) (x).ne_cbnrestab #define NE_SEGTAB(x) (x).ne_segtab #define NE_RSRCTAB(x) (x).ne_rsrctab #define NE_RESTAB(x) (x).ne_restab #define NE_MODTAB(x) (x).ne_modtab #define NE_IMPTAB(x) (x).ne_imptab #define NE_NRESTAB(x) (x).ne_nrestab #define NE_CMOVENT(x) (x).ne_cmovent #define NE_ALIGN(x) (x).ne_align #define NE_CRES(x) (x).ne_cres #define NE_RES(x) (x).ne_res #define NE_EXETYP(x) (x).ne_exetyp #define NE_USAGE(x) (WORD)*((WORD *)(x)+1) #define NE_PNEXTEXE(x) (WORD)(x).ne_cbenttab #define NE_ONEWEXE(x) (WORD)(x).ne_crc #define NE_PFILEINFO(x) (WORD)((DWORD)(x).ne_crc >> 16) /* * Target operating systems */ #define NE_UNKNOWN 0x0 /* Unknown (any "new-format" OS) */ #define NE_OS2 0x1 /* Microsoft/IBM OS/2 (default) */ #define NE_WINDOWS 0x2 /* Microsoft Windows */ #define NE_DOS4 0x3 /* Microsoft MS-DOS 4.x */ /* * Format of NE_FLAGS(x): * * p Not-a-process * x Unused * e Errors in image * x Unused * b Bound as family app * ttt Application type * f Floating-point instructions * 3 386 instructions * 2 286 instructions * 0 8086 instructions * P Protected mode only * p Per-process library initialization * i Instance data * s Solo data */ #define NENOTP 0x8000 /* Not a process */ #define NEIERR 0x2000 /* Errors in image */ #define NEBOUND 0x0800 /* Bound as family app */ #define NEAPPTYP 0x0700 /* Application type mask */ #define NENOTWINCOMPAT 0x0100 /* Not compatible with P.M. Windowing */ #define NEWINCOMPAT 0x0200 /* Compatible with P.M. Windowing */ #define NEWINAPI 0x0300 /* Uses P.M. Windowing API */ #define NEFLTP 0x0080 /* Floating-point instructions */ #define NEI386 0x0040 /* 386 instructions */ #define NEI286 0x0020 /* 286 instructions */ #define NEI086 0x0010 /* 8086 instructions */ #define NEPROT 0x0008 /* Runs in protected mode only */ #define NEPPLI 0x0004 /* Per-Process Library Initialization */ #define NEINST 0x0002 /* Instance data */ #define NESOLO 0x0001 /* Solo data */ struct new_seg /* New .EXE segment table entry */ { unsigned short ns_sector; /* File sector of start of segment */ unsigned short ns_cbseg; /* Number of bytes in file */ unsigned short ns_flags; /* Attribute flags */ unsigned short ns_minalloc; /* Minimum allocation in bytes */ }; #define NS_SECTOR(x) (x).ns_sector #define NS_CBSEG(x) (x).ns_cbseg #define NS_FLAGS(x) (x).ns_flags #define NS_MINALLOC(x) (x).ns_minalloc /* * * x Unused * h Huge segment * c 32-bit code segment * d Discardable segment * DD I/O privilege level (286 DPL bits) * c Conforming segment * r Segment has relocations * e Execute/read only * p Preload segment * P Pure segment * m Movable segment * i Iterated segment * ttt Segment type */ #define NSTYPE 0x0007 /* Segment type mask */ #define NSCODE 0x0000 /* Code segment */ #define NSDATA 0x0001 /* Data segment */ #define NSITER 0x0008 /* Iterated segment flag */ #define NSMOVE 0x0010 /* Movable segment flag */ #define NSSHARED 0x0020 /* Shared segment flag */ #define NSPRELOAD 0x0040 /* Preload segment flag */ #define NSEXRD 0x0080 /* Execute-only (code segment), or * read-only (data segment) */ #define NSRELOC 0x0100 /* Segment has relocations */ #define NSCONFORM 0x0200 /* Conforming segment */ #define NSDPL 0x0C00 /* I/O privilege level (286 DPL bits) */ #define SHIFTDPL 10 /* Left shift count for SEGDPL field */ #define NSDISCARD 0x1000 /* Segment is discardable */ #define NS32BIT 0x2000 /* 32-bit code segment */ #define NSHUGE 0x4000 /* Huge memory segment, length of * segment and minimum allocation * size are in segment sector units */ #define NSPURE NSSHARED /* For compatibility */ #define NSALIGN 9 /* Segment data aligned on 512 byte boundaries */ #define NSLOADED 0x0004 /* ns_sector field contains memory addr */ struct new_segdata /* Segment data */ { union { struct { unsigned short ns_niter; /* number of iterations */ unsigned short ns_nbytes; /* number of bytes */ char ns_iterdata; /* iterated data bytes */ } ns_iter; struct { char ns_data; /* data bytes */ } ns_noniter; } ns_union; }; struct new_rlcinfo /* Relocation info */ { unsigned short nr_nreloc; /* number of relocation items that */ }; /* follow */ struct new_rlc /* Relocation item */ { char nr_stype; /* Source type */ char nr_flags; /* Flag byte */ unsigned short nr_soff; /* Source offset */ union { struct { char nr_segno; /* Target segment number */ char nr_res; /* Reserved */ unsigned short nr_entry; /* Target Entry Table offset */ } nr_intref; /* Internal reference */ struct { unsigned short nr_mod; /* Index into Module Reference Table */ unsigned short nr_proc; /* Procedure ordinal or name offset */ } nr_import; /* Import */ struct { unsigned short nr_ostype; /* OSFIXUP type */ unsigned short nr_osres; /* reserved */ } nr_osfix; /* Operating system fixup */ } nr_union; /* Union */ }; #define NR_STYPE(x) (x).nr_stype #define NR_FLAGS(x) (x).nr_flags #define NR_SOFF(x) (x).nr_soff #define NR_SEGNO(x) (x).nr_union.nr_intref.nr_segno #define NR_RES(x) (x).nr_union.nr_intref.nr_res #define NR_ENTRY(x) (x).nr_union.nr_intref.nr_entry #define NR_MOD(x) (x).nr_union.nr_import.nr_mod #define NR_PROC(x) (x).nr_union.nr_import.nr_proc #define NR_OSTYPE(x) (x).nr_union.nr_osfix.nr_ostype #define NR_OSRES(x) (x).nr_union.nr_osfix.nr_osres /* * Format of NR_STYPE(x): * * xxxxx Unused * sss Source type */ #define NRSTYP 0x0f /* Source type mask */ #define NRSBYT 0x00 /* lo byte */ #define NRSSEG 0x02 /* 16-bit segment */ #define NRSPTR 0x03 /* 32-bit pointer */ #define NRSOFF 0x05 /* 16-bit offset */ #define NRSPTR48 0x0B /* 48-bit pointer */ #define NRSOFF32 0x0D /* 32-bit offset */ /* * Format of NR_FLAGS(x): * * xxxxx Unused * a Additive fixup * rr Reference type */ #define NRADD 0x04 /* Additive fixup */ #define NRRTYP 0x03 /* Reference type mask */ #define NRRINT 0x00 /* Internal reference */ #define NRRORD 0x01 /* Import by ordinal */ #define NRRNAM 0x02 /* Import by name */ #define NRROSF 0x03 /* Operating system fixup */ /* Resource type or name string */ struct rsrc_string { char rs_len; /* number of bytes in string */ char rs_string[ 1 ]; /* text of string */ }; #define RS_LEN( x ) (x).rs_len #define RS_STRING( x ) (x).rs_string /* Resource type information block */ struct rsrc_typeinfo { unsigned short rt_id; unsigned short rt_nres; long rt_proc; }; #define RT_ID( x ) (x).rt_id #define RT_NRES( x ) (x).rt_nres #define RT_PROC( x ) (x).rt_proc /* Resource name information block */ struct rsrc_nameinfo { /* The following two fields must be shifted left by the value of */ /* the rs_align field to compute their actual value. This allows */ /* resources to be larger than 64k, but they do not need to be */ /* aligned on 512 byte boundaries, the way segments are. */ unsigned short rn_offset; /* file offset to resource data */ unsigned short rn_length; /* length of resource data */ unsigned short rn_flags; /* resource flags */ unsigned short rn_id; /* resource name id */ unsigned short rn_handle; /* If loaded, then global handle */ unsigned short rn_usage; /* Initially zero. Number of times */ /* the handle for this resource has */ /* been given out */ }; #define RN_OFFSET( x ) (x).rn_offset #define RN_LENGTH( x ) (x).rn_length #define RN_FLAGS( x ) (x).rn_flags #define RN_ID( x ) (x).rn_id #define RN_HANDLE( x ) (x).rn_handle #define RN_USAGE( x ) (x).rn_usage #define RSORDID 0x8000 /* if high bit of ID set then integer id */ /* otherwise ID is offset of string from the beginning of the resource table */ /* Ideally these are the same as the */ /* corresponding segment flags */ #define RNMOVE 0x0010 /* Moveable resource */ #define RNPURE 0x0020 /* Pure (read-only) resource */ #define RNPRELOAD 0x0040 /* Preloaded resource */ #define RNDISCARD 0xF000 /* Discard priority level for resource */ /* Resource table */ struct new_rsrc { unsigned short rs_align; /* alignment shift count for resources */ struct rsrc_typeinfo rs_typeinfo; }; #define RS_ALIGN( x ) (x).rs_align Figure D-5. The NEWEXE.H C header file, which defines the structure of the tables in OS/2 load modules. Appendix E Module Definition File Syntax Module definition (DEF) files are simple ASCII text files that are interpreted by the Linker during the construction of an application program, dynlink library, or device driver. The directives in DEF files cause information to be built into the executable file's header, which is later interpreted by the system when the program, library, or driver is loaded. Enter all DEF file directives and keywords in uppercase letters. File, segment, group, and procedure names can be lowercase or uppercase. Lines beginning with a semicolon (;) are treated as comments. The directives documented in this appendix are listed in the following table, Figure E-1. Directive Description CODE Assigns characteristics to code segments DATA Assigns characteristics to data segments DESCRIPTION Embeds text in executable file EXETYPE Specifies host operating system EXPORTS Names functions exported for dynamic linking by other programs HEAPSIZE Specifies initial size of local heap (C programs only) IMPORTS Names functions that will be dynamically linked at load time LIBRARY Builds dynlink library or device driver NAME Builds application program OLD Specifies ordinal compatibility with previous version of dynlink library PROTMODE Flags file as executable in protected mode only REALMODE Allows file to be executed in real mode SEGMENTS Assigns characteristics to selected segments STACKSIZE Specifies size of stack used by primary thread STUB Embeds MS-DOScompatible program in new executable file Figure E-1. DEF file directives documented in this appendix. Each directive is explained in a separate entry with its options and keywords. The entries are in alphabetic order and do not reflect the usual order of directives in an actual DEF file. Default keywords are shown in boldface. Directives, options, and keywords that are relevant only to real mode Microsoft Windows applications or Windows dynlink libraries are omitted. For summaries of DEF file syntax, see p. 33 (for applications) and p. 466 (for dynlink libraries). CODE Declares the characteristics of all segments whose classnames are 'CODE' or end with 'CODE'. Syntax: CODE [load] [execute] [privilege] [conforming] load specifies when the segment is to be brought into memory: PRELOAD Loaded when the process is started LOADONCALL Not loaded until it is first accessed execute controls whether the segment can be read as well as executed: EXECUTEONLY Can be executed but not read EXECUTEREAD Can be executed and read privilege specifies whether the segment has I/O privilege (runs at ring 2) and can use the instructions IN, INS, OUT, OUTS, CLI, and STI: IOPL Has I/O privilege NOIOPL Does not have I/O privilege conforming determines whether the segment can be called from ring 2 and ring 3 or only from ring 3: CONFORMING Can be called from either ring 2 or ring 3 and executes at the caller's privilege level NONCONFORMING Can be called only from ring 3 and executes at its natural privilege level Note: If the CODE directive is absent, the default attributes for all segments whose classnames are 'CODE' or end with 'CODE' are LOADONCALL EXECUTEREAD NOIOPL NONCONFORMING. Related directives: DATA, SEGMENTS. DATA Declares the characteristics of all segments whose classnames are not 'CODE' or do not end with 'CODE'. Syntax: DATA [load] [readwrite] [instance] [shared] [privilege] load specifies when the segment is to be brought into memory: PRELOAD Loaded when the process is started LOADONCALL Not loaded until it is first accessed readwrite controls whether the segment can be written as well as read: READONLY Can be read but not written READWRITE Can be read and written instance describes the automatic data segment (DGROUP): NONE No DGROUP present (dynlink libraries only) SINGLE Single copy of DGROUP shared by all instances of the program or library (default for dynlink libraries) MULTIPLE Separate copy of DGROUP created for each instance of the program or library (default for applications) shared specifies whether the segment can be shared among all instances of the program: SHARED One copy of the segment shared among all instances of a program (default for dynlink libraries) NONSHARED Separate copy of the segment created for each instance of the program (default for applications) privilege specifies whether access to the data segment is limited to code running at ring 2: IOPL Can be accessed only by code running in ring 2 NOIOPL Can be accessed by ring 2 or ring 3 code Notes: The shared option is ignored if the segment is READONLY; READONLY segments are always SHARED. If a shared keyword is present but an instance keyword is not, the Linker overrides the default for the absent keyword to make it consistent with the other. For example, DATA SINGLE forces SHARED, and DATA MULTIPLE forces NONSHARED. Similarly, DATA SHARED forces SINGLE, and DATA NONSHARED forces MULTIPLE. If conflicting keywords are present, the Linker issues a warning. If the DATA directive is absent, the default attributes of all segments not covered by the CODE directive are LOADONCALL READWRITE MULTIPLE NONSHARED NOIOPL for applications and LOADONCALL READWRITE SINGLE SHARED NOIOPL for dynlink libraries. Related directives: CODE, SEGMENTS. DESCRIPTION Allows source control or copyright information to be embedded in the executable file. Syntax: DESCRIPTION 'string' Note: The string must be enclosed in single quotation marks and can be a maximum of 128 bytes. It is not available to the program itself and does not occupy space in the program's code or data segments. Related directives: None. EXETYPE Specifies the operating system with which the application or dynlink library will be used. Syntax: EXETYPE system system specifies the host operating system: OS2 Microsoft OS/2 version 1.0 or later WINDOWS Microsoft Windows DOS4 Multitasking MS-DOS (currently sold only by European OEMs, not the same as the USA MS-DOS/PC-DOS version 4) Note: If the EXETYPE directive is absent, OS2 is assumed. Related directives: REALMODE, PROTMODE. EXPORTS Makes functions available for dynamic linking by other programs or dynlink libraries, and/or signals the OS/2 loader to set up call gates for routines within an IOPL segment. Syntax: EXPORTS statement statement . . . where each statement takes the following form: entryname [=internalname] [@ordinal [RESIDENTNAME]] [stackparams] entryname is the name by which the function can be imported (with the IMPORTS directive) by other programs or dynlink libraries, and/or (in the case of functions being exported from an IOPL segment) the name by which the function is called from ring 3 segments. internalname is the exported function's actual name within the source code. ordinal is an integer number assigned to the function to allow faster dynamic linking. RESIDENTNAME causes the system loader to keep the exported function's name resident in memory at all times while the module is in use. stackparams declares the number of stack words that are passed to the routine by the caller (relevant for exported IOPL routines only). Notes: If entryname and internalname are the same, =internalname can be omitted. To minimize synchronization problems between the source code for dynlink libraries and client processes, use ordinal numbers only to refer to functions in modules that are very stable. The stackparams information is built into the call gate created at load time for functions exported from an IOPL segment. The hardware automatically copies the function's arguments from the ring 3 stack to the ring 2 stack. Related directive: IMPORTS. HEAPSIZE Defines the initial size of a C application's local heap. Syntax: HEAPSIZE bytes Notes: The bytes parameter is assumed to be a decimal number. You can specify hex and octal numbers using C notation. The local heap is located in DGROUP along with the near data and default stack, and memory can be allocated from the heap with malloc() and related functions. Regardless of the heap's initial size, it is expanded at run time whenever necessary until DGROUP has reached its maximum size of 65,536 bytes. If HEAPSIZE is absent, the default initial heap size is 0 bytes. Related directive: STACKSIZE. IMPORTS Identifies functions to be imported from other modules (typically dynlink libraries). When functions are imported, the addresses in the function calls are bound at load time rather than at link time. Syntax: IMPORTS statement statement . . . where each statement takes the following form: [internalname=] module.entryname | module.ordinal module is the filename and module name (which must be the same) of the dynlink library that contains the imported function. internalname is the unique ASCII name by which the program being linked refers to the function to be imported. entryname is the name by which the function was exported when the dynlink library was linked. ordinal is an integer number that identifies the imported function. Notes: When internalname and entryname are the same (as they are in most cases), internalname= can be omitted. When an ordinal is used, it must correspond with the ordinal specified in the dynlink library's EXPORTS directive for the same function. Use of ordinals speeds up the loadtime dynlink process but introduces additional dependence between client process and dynlink libraries that complicates the maintenance of both. When an import library (such as OS2.LIB) is used in linking, IMPORTS directives are not needed for those functions that have import reference records in the library. Related directive: EXPORTS. LIBRARY Specifies that a dynlink library or device driver is being built. Syntax: LIBRARY [modulename] [initialization] modulename identifies the module when importing or exporting functions. initialization is relevant only for dynlink libraries: INITGLOBAL initialization entry point is only called when the dynlink library is first loaded. INITINSTANCE initialization entry point is called when dynlink library is first loaded and each time a new application dynamically links to the library. Notes: The modulename should ordinarily be the same as the name of the executable file. If modulename is absent, the filename (without the extension) is used by default. The NAME and LIBRARY directives cannot occur together in the same DEF file. If LIBRARY is present, it must precede all other directives. If both NAME and LIBRARY are absent, NAME is assumed. Related directive: NAME. NAME Indicates that an application program is being built. Syntax: NAME [modulename] [apptype] modulename identifies the module to the system loader when multiple instances of the same program are running simultaneously. apptype declares the program's compatibility with the Presentation Manager: WINDOWAPI Application is written to Presentation Manager conventions. WINDOWCOMPAT Application uses subset of Vio, Mou, and Kbd calls and can run in a Presentation Manager window. NOTWINDOWCOMPAT Application uses Vio, Mou, or Kbd calls that are not allowed in a Presentation Manager window, or accesses the video hardware directly, and must be run in its own screen group. Notes: The modulename should ordinarily be the same as the name of the executable file. If modulename is absent, the filename (without the extension) is used by default. The apptype corresponds to the codes returned by the API function DosQAppType. There is no default for apptype; if it is absent, DosQAppType returns "unknown" and the application is run full-screen. The NAME and LIBRARY directives cannot occur together in the same DEF file. If NAME is present, it must precede all other directives. If both NAME and LIBRARY are absent, NAME is assumed. Related directive: LIBRARY. OLD Used when building a dynlink library to preserve the association between ordinals and entry points in a previous version of the library. Syntax: OLD 'filename.DLL' Notes: The filename is the name of an existing dynlink library; it must be enclosed in single quotes, and the extension must be explicit. If filename is not found in the current directory, the Linker searches each directory named in the PATH environment variable. When the OLD directive is used, exported functions in the new library that match exported names in the old library are assigned the same ordinal numbers, unless either of the following is true: The function name in the old library has no associated ordinal. You use EXPORTS to assign explicitly an ordinal number to the function in the the new library's DEF file. Related directives: EXPORTS, IMPORTS. PROTMODE Indicates that the application can be executed only in protected mode. Syntax: PROTMODE Note: If both PROTMODE and REALMODE directives are absent, the system loader assumes that the module can be run in either real mode or protected mode. Related directives: EXETYPE, REALMODE. REALMODE Indicates that the application can be executed in real mode under MS-DOS or in the DOS compatibility environment under OS/2. Syntax: REALMODE Note: If both PROTMODE and REALMODE directives are absent, the system loader assumes that the module can be run in either real mode or protected mode. Related directives: EXETYPE, PROTMODE. SEGMENTS Overrides default segment characteristics or those assigned with CODE and DATA directives on a segment-by-segment basis. Can also be used to control segment order. Syntax: SEGMENTS statement statement . . . where each statement takes the following form: segname [CLASS ['classname']] [attribute] segname is the segment name assigned with the SEGMENT mnemonic (MASM programs), by the compiler according to the memory model (C programs), or by the programmer with /NT or /ND switches. CLASS assigns a class name to the segment so that the Linker will group it with other segments with the same class name. attribute can be any combination of the following alternative keywords: PRELOAD | LOADONCALL READONLY | READWRITE EXECUTEONLY | EXECUTEREAD IOPL | NOIOPL CONFORMING | NONCONFORMING SHARED | NONSHARED Notes: The segname may be optionally enclosed in single quote marks; it must be quoted if segname is CODE or DATA to differentiate it from the CODE and DATA directives. Typical class names are 'CODE', 'DATA', and 'FAR_DATA'. If CLASS is absent, or if CLASS is present but classname is absent, a class name of 'CODE' is assumed. See the entries for the CODE and DATA directives for the meanings of each attribute keyword. If no attribute keywords are present, the segment is assigned default attributes depending on its class. If at least one attribute is supplied, the default CODE or DATA attributes are not used, and any attribute that is not explicit is taken from the following list: LOADONCALL EXECUTEREAD (if class CODE) READWRITE (if class DATA) NONSHARED NONCONFORMING (if class CODE) NOIOPL Related directives: CODE, DATA. STACKSIZE Defines the size of the application's default stack, that is, the stack that is used by the application's primary thread when it is first entered from OS/2. Syntax: STACKSIZE bytes Notes: The bytes parameter is assumed to be a decimal number. You can specify hex and octal numbers using C notation. If the STACKSIZE directive is present, it overrides any stack segment declared in the application source code or with the Linker /STACK switch. Related directive: HEAPSIZE. STUB Specifies an MS-DOScompatible program that is embedded into the new EXE or DLL file. The stub program receives control if the program being linked is executed under MS-DOS or in the DOS compatibility environment under OS/2. Syntax: STUB 'filename.EXE' Notes: The filename must be enclosed in single quotation marks, and the extension must be explicit. If filename is not found in the current directory, the Linker searches the directories listed in the PATH environment variable. If the STUB directive is absent, the Linker automatically inserts an MS-DOScompatible program that displays the message This program cannot be run in DOS mode and terminates. Related directives: None. Glossary 3.x Box See DOS compatibility environment. alias A selector (and associated descriptor) that refers to the same physical memory as another selector and descriptor, but with different access rights or characteristics. allocation unit See cluster. anonymous pipe A high performance, first-in-first-out mechanism for interprocess communications. Anonymous pipes can be used only by closely related processes. Application Program Interface (API) The calling convention by which OS/2 makes its services available to application programs. ASCII mode The default mode for keyboard input. In ASCII mode (also known as cooked mode), some keys and key sequences (such as the Enter key, Ctrl-S, Ctrl-P, Ctrl-Q, and Ctrl-Z) are intercepted and receive special treatment by the operating system. ASCIIZ A character string that is terminated by a zero byte. asynchronous access File access in which the operating system returns control to the requesting process immediately and notifies the process by some other means when the read or write is complete. background process A process associated with a screen group that is not currently being displayed. base device drivers The device drivers for the keyboard, display, disk, printer, and clock that are always loaded during system initialization. binary mode A mode that can be selected by programs for keyboard input. In binary mode (also known as raw mode), all characters are passed through to the application, and the editing keys are not active. BIOS parameter block (BPB) A structure that describes the characteristics of a logical disk volume. A copy of the BPB for a specific volume is always found in that volume's boot sector and can be used to calculate the locations of the FAT, root directory, and file storage areas. block To prevent a thread from executing until a resource becomes available, an event occurs, or a timeout elapses. block device A peripheral device (such as a disk drive) that transfers data in chunks of fixed size rather than one byte at a time. boot sector Logical sector zero of a disk volume, which contains information about the volume's characteristics and a short bootstrap program. BPB See BIOS parameter block. call gate A special LDT or GDT descriptor that represents a procedure entry point rather than a memory segment. Call gates provide controlled transitions from higher-numbered rings (lower privilege levels) to lower-numbered rings (higher privilege levels). captive thread A thread that is created within a dynlink library on behalf of an application (but without its knowledge) and that executes only within the library; also, a thread that is used to call an API function and that is not allowed to return from the function call until a specific event occurs. character device A peripheral device that transfers data one byte at a time, such as the keyboard or serial port. child process A process created by another process (called its parent process). cluster The unit of allocation of file storage on a logical disk volume; each file consists of one or more clusters. The number of sectors in a cluster is always a power of 2. code page A table that maps hardware-specific codes (such as keyboard scan codes) to character codes. command subtree A process and all its descendants. compatibility box See DOS compatibility environment. context switch The act of suspending the execution of a thread, saving its context, and giving control of the CPU to another thread (which might or might not belong to the same process). cooked mode See ASCII mode. counted string A string that is represented by a byte containing the length of the string (not including the length byte itself), followed by the characters of the string. critical error See hard error. critical section A sequence of code that manipulates a resource (such as a data structure) in a nonreentrant manner. daemon process A process that performs a utility function without interaction with the user, for example, the OS/2 swapper process. descriptor table A table that maps selectors to physical segments of memory. See also global descriptor table; local descriptor table. device driver A program that transforms I/O requests from the OS/2 kernel into commands to the hardware. device monitor A process that registers itself with the OS/2 kernel and with a device driver to track or modify the raw data stream of that driver. disk bootstrap A small program in the boot sector which brings the initial portions of the operating system into memory for execution. DLL See dynamic link library. DOS compatibility environment A special OS/2 screen group that emulates an MS-DOS environment and can be used to run one real mode application at a time alongside one or more protected mode applications. dynamic link A reference within a program to an external procedure or value that is resolved at load time or run time. dynamic link library A subroutine package that is bound to an application at load time or during execution, rather than at link time. dynamic link subsystem A module that manages a resource and makes the capabilities of the resource available to the rest of the system by exporting function names that can be dynamically linked. dynlink See dynamic link. encapsulation The principle of hiding the internal implementation of a program, function, or service so that its clients can tell what it does but not how it accomplishes its task. environment strings A block of ASCIIZ strings that are associated with each process and inherited from the parent process. ExitList A list of procedures, registered by the process, that are called by OS/2 when the process terminates for any reason. Family API The subset of OS/2 API functions that have direct counterparts in the functions supported by MS-DOS version 2.0 or later and in the services of the IBM PC ROM BIOS. Family application A program that has been processed by the BIND utility so that it can run in either protected mode or real mode. A Family application must not contain any machine instructions specific to the 80286 or 80386 and can call only those OS/2 services that are members of the Family API. fast-safe RAM semaphore A semaphore designed to meet the needs of dynlink libraries. It combines the speed of RAM semaphores and the advantages of system semaphores "counting" and cross-process usability. See also RAM semaphore; system semaphore. FAT See file allocation table. file allocation table (FAT) A table in the control area of a logical disk volume that defines the current usage for each cluster in that volume's file storage area. file handle A value returned by OS/2 when a file is opened or created; used in subsequent function calls to represent the file. file locking The restricting of access to a file by other processes. file pointer A value, maintained internally by OS/2 for each file handle, which determines the starting point for the next read or write operation associated with that handle. file system The component of the operating system that manages files and directories and translates file function calls by an application into requests to the disk driver for transfers of logical sectors; also, the interdependent tables and directories on a disk that define the location of each file and directory on that disk. file system name space All the possible name constructions that have the format of valid filenames. forced event An event or action that is forced upon a thread or a process from an external source; for example, a Ctrl-C or Ctrl-Break signal. foreground process A process that is executing in the currently visible screen group and can therefore interact with the user. GDT See global descriptor table. general protection (GP) fault An error that occurs when a program executes an instruction that is not allowed at its privilege level, uses an invalid memory address, or accesses a valid address in an invalid manner. When a GP fault occurs, OS/2 receives control and terminates the process. giveaway shared memory A memory block for which the allocating process can obtain a selector that can be passed to and used by another process. global data segment A data segment that is shared among all instances of a dynlink library; that is, only one copy of the segment exists, regardless of the number of client processes that are bound to the library. See also instance data segment. global descriptor table (GDT) The descriptor table that defines the memory segments owned by the operating system and its drivers. global heap The RAM which can be dynamically allocated for use by OS/2 subsystems and processes. The global heap is administered by the OS/2 memory manager. global initialization Execution of a dynlink library's initialization routine only when the library is initially loaded. See also instance initialization. GP fault See general protection fault. graphical user interface A term commonly used for a user interface that runs in graphics mode and relies on pointing devices, dialog boxes, menus, and graphical objects such as icons, buttons, and scroll bars to interact with the user. OS/2's graphical user interface is a component of the Presentation Manager. handle A binary value that represents a system resource, such as a file, pipe, semaphore, or queue. hard error An error that arises from a hardware error, resource conflict, or program bug that a process cannot be expected to recover from or that requires user intervention. hot key The keystroke or key sequence that activates a background process (usually a monitor) or the Task Manager. huge segments Memory blocks consisting of two or more physical segments that are assigned logically consecutive selectors. import libraries Special object module libraries that contain reference records for the entry points in dynlink libraries. installable device drivers Device drivers, such as the mouse driver, that are loaded during system initialization as the result of a DEVICE directive in the CONFIG.SYS file. instance data segment A memory segment that holds data specific to each instance of a dynlink library; that is, a separate copy of the segment is created for each client process that dynamically links to the library. See also global data segment. instance initialization Execution of a dynlink library's initialization routine each time a new client process dynamically links to the library. See also global initialization. interactive process A program that requires (or can require) interaction with the user and that calls (or can call) Kbd, Mou, or Vio functions. See also daemon process. interleaving The mapping of consecutive physical disk sector addresses onto noncontiguous physical sectors to improve performance. interprocess communication (IPC) The exchange of information between two or more processes via semaphores, pipes, queues, shared memory, or signals. Threads within the same process can also use IPC methods to communicate. I/O privilege level (IOPL) A privilege level that allows the instructions CLI, STI, IN, INS, OUT, and OUTS to be executed without causing a GP fault. IPC See interprocess communication. kernel The part of OS/2 that executes at the highest privilege level (ring 0) and contains the scheduler, file system, memory manager, and other vital services. Kernel application A program that executes only in protected mode and uses the OS/2 kernel API for keyboard, screen, and mouse I/O. LDT See local descriptor table. loadtime dynamic linking The establishment of a link between a process and a dynamic link library when the process is first loaded into memory. The occurrence of this linkage at load time is controlled by information in the program file header. local descriptor table (LDT) A table that exists for each active process and that defines the memory which is owned or shared by that process. logical device A symbolic name for a physical device, such as the name COM1 for the first serial port. logical drive A file system, represented by a letter and a colon (such as A:), containing a boot sector, root directory, file allocation table, and zero to many subdirectories and files. A single physical drive can be represented as several logical drives. memory manager The component of OS/2 that is responsible for dynamic allocation of memory from the global heap. See also physical memory manager; virtual memory manager. memory overcommit The allocation of more memory to a process than physically exists. memory protection The ability of the hardware to detect any attempt by an application program to access memory that does not belong to it or for which it has an insufficient privilege level. module definition file A file that describes the segment characteristics of an OS/2 application program or dynlink library. The DEF file is processed by the Linker during the creation of an executable file. monitor dispatcher A component of the OS/2 kernel that arbitrates among device monitor applications, the kernel, and a device driver and moves data from the driver through the buffers of one or more device monitors and back to the driver. multitasking The sharing of CPU time among two or more tasks so that they appear to be running simultaneously. named pipe A first-in-first-out interprocess communication mechanism that is similar to anonymous pipes but is not restricted to use by closely related processes. object name buffer The buffer that receives the name of a dynlink library or entry point. parent process A process that creates another process (called a child process). physical memory The RAM (random access memory) physically installed in the machine. physical memory manager The component of OS/2 that is responsible for allocating physical memory and managing descriptor tables. PID See process ID. pipe See anonymous pipe; named pipe. preemptive multitasking The ability of the operating system to divide CPU time among two or more tasks without their cooperation; sometimes called time-slicing. Presentation Manager The graphical user interface for OS/2. See also graphical user interface. primary thread The thread that begins execution of a process and is used to field signals directed to that process. priority The numeric value assigned to each active thread in the system. Threads with a higher priority are assigned the CPU in preference to those with a lower priority. privilege level One of four levels (or rings) at which code executes in protected mode. The privilege level determines which machine instructions can be executed and which memory segments can be accessed. process The executing instance of a program file. A process can own memory, files, pipes, semaphores, and other system resources and can contain more than one thread of execution. process ID (PID) A number that OS/2 assigns to a process when the process is created. Each active process has a different PID. process subtree See command subtree. protected mode The operating mode of the 80286 microprocessor that supports memory protection, virtual memory, and privilege levels. queue An ordered list of data segments or records (called queue elements). RAM semaphore A semaphore that is referenced by its address; such semaphores are fast but have limited functionality. See also fast-safe RAM semaphore; system semaphore. random access The reading and writing of nonsequential records. raw mode See binary mode. real mode The operating mode of the 80286 microprocessor that emulates the operation of an Intel 8086/88 processor. Memory protection, virtual memory, and privilege levels are not supported in real mode. record locking The restricting of access to a region of a file by other processes. ring See privilege level. runtime dynamic linking The establishment of a link between a process and a dynamic link library after the process has begun execution. The process provides OS/2 with a module name and an entry point name, and the operating system returns an entry point address. scheduler The part of OS/2 that decides which thread to run and how long to run it before assigning the CPU to another thread; also, the part of OS/2 that determines the priority value for each thread. screen group One or more processes that share (generally in a serial fashion) a single logical screen, keyboard, and mouse; also called a session. search handle A binary value associated with a search context. OS/2 returns such a handle when a search for a particular set of files or directories is initiated. sector A unit of storage on a disk; usually 512 bytes. segment A contiguous block of memory that is represented by a selector. segment swapping The transfer of segments between physical memory and a disk file by the virtual memory manager. selector A value that can be loaded into a segment register in protected mode and that indexes an entry in a descriptor table. semaphore An interprocess communication mechanism that has only two states and that is commonly used to signal between threads or processes or to represent ownership of a resource. sequential access The reading or writing of consecutive data in a file. session See screen group. SFT See system file table. shared memory A memory segment that can be accessed by more than one process. signaling The use of semaphores to indicate that an event has occurred or that an action should be taken. signal An interprocess communication mechanism that is analogous to a hardware interrupt. stack frame The portion of a thread's stack that contains the parameters and local variables for a particular procedure. static linking The resolution of external references during the creation of an executable file. See also dynamic link. subsystem See dynamic link subsystem. swapping See segment swapping. synchronous access File access during which the thread requesting the read or write operation is suspended until the transfer is complete. See also asynchronous access. system file table (SFT) An internal OS/2 table that contains an entry for every file or device that any given process in the system is using. system semaphore A semaphore that is referred to by name and whose storage is managed by OS/2; more powerful than a RAM semaphore but somewhat slower. See also fast-safe RAM semaphore; RAM semaphore. task See process. Task Manager The Presentation Manager program that lists all currently running programs and allows the user to switch among them. Roughly equivalent to the Session Manager in OS/2 version 1.0. thread A point of execution within a process; each thread is associated with a priority, register contents, and a state (blocked, ready to execute, or executing). A process always contains one or more threads. thread ID A number that identifies a particular thread within a process. timer tick A hardware interrupt that allows the operating system to regain control at predetermined intervals. timeslice The amount of execution time that the scheduler will give a thread before reassigning the CPU to another thread of equal priority. time-slicing See preemptive multitasking. turnaround character Logical end-of-line character, usually the Enter key. virtual memory The memory space allocated to and used by a process, which may be larger than available physical memory. virtual memory manager The component of OS/2 that is responsible for moving memory segments between physical memory and the disk swap file to provide the illusion of a larger memory space. write-through The immediate transfer of data from RAM to disk at the time of a process's write request, as opposed to the deferral of the actual write operation by buffering the data within OS/2. Index Special Characters # 61, 67 * 63 *- 64 + 58, 61, 63 -+ 64 . 199 .. 199 : 132, 165 ; 58, 64 < 330 > 330 >> 330 @ 58, 64 \ 172, 189 | 330 2> 330 2>> 330 .286 directive 44, 69 3.x box 10 A ABIOS. See ROM ABIOS (advanced basic I/O system) ABIOSCall (DevHlp function) 389, 662 ABIOSCommonEntry (DevHlp function) 389, 663 AllocGDTSelector (DevHlp function) 386, 392, 66364 AllocPhys (DevHlp function) 361, 386, 39192, 664 AllocReqPacket (DevHlp function) 388, 39495, 665 all-points-addressable (graphics) mode, video 95, 96 all-points-addressable (APA) output devices 5 alphanumeric (text) mode, video 95 anonymous pipes 28083 APA. See all-points-addressable (graphics) mode API. See Application Program Interface (API) functions API.LIB file 6566 application program(s) 2747 data flow between character device and 419 data flow between character device and, when device monitor is registered 421 entry conditions 3739 example creation of, using programming tools 67, 68, 6970 execution of, and Application Program Interface 3941 files 3435 loading 3537 module definition files 32 syntax summary 33 place in system layering 11, 12, 13 privilege levels 67, 13 processing typical I/O requests from 39799 process termination 42 sample HELLO file 4247 source structure 2831 types of, and complexity levels 2728 using IOPL segments in 3056 writing 49 (see also programming tools) Application Program Interface (API) functions 5. See also Family API (FAPI) available during device driver initialization 362 device monitor 420 direct disk access 202 Dos functions listed by functional group 498502 file management 132 IOPL segments 303, 304, 305 Kbd (keyboard) 75, 79 listed alphabetically 49297 manipulation of drives, directories, and volume labels 167 memory management 210 Mou mouse 8687, 88 multitasking management 231, 232 to obtain information on environment, system state, and capabilities 40 pipe management 284 process execution and 3941 queue management 289 quick reference 49091 reference 489619 runtime dynamic linking 461 semaphore management 272 for serial port control 127 time, date, and programmable timers 318 Vio video display 1034 ARGC.ASM routine 15759, 190, 469, 470, 476 argument strings 38 ARGC.ASM routine returning argument count 15759 ARGV.ASM routine returning argument pointer 15962 hex and ASCII dump of program 39 ARGV.ASM routine 15962, 190, 469, 470, 476 ASCII character set 7078 ASCII "cooked" mode 74, 76, 78, 79 ASCII text files 18, 61, 66 ASCIIZ null-terminated strings 37, 38 ASMHELP.DLL dynamic link library 46976, 481 ASMHELP.ASM source code 47075 ASMHELP.DEF module definition file 476 use in SHOWARGS.ASM 47780 assembly language programs. See also Macro Assembler (MASM) ASMHELP.ASM 47075 CINIT.ASM 484 DUMBTERM.ASM 26068 DUMP.ASM 14956 dynamic linking of 460 EXECSORT.ASM 34145 HELLO.ASM 4246 lowercasing filter in 336 memory image after loading 36 memory model 30, 31 PORTIO.ASM 3079 PORTS.ASM 31115 PROTO.ASM 33134 registration of handler for Ctrl-C signals 29798 SHOWARGS.AMS 47780 SNAP.ASM 42944 structure 28, 29 for synchronous, sequential file access 14143 TEMPLATE.ASM 37783 TINYCMD.ASM 24754 TINYDISK.ASM 40417 WHEREIS.ASM 17989 assume statement 45 asynchronous access, defined 140 AttachDD (DevHlp function) 389, 39697, 66566 AUTOEXEC.BAT file 17 B Basic Keyboard Subsystem 84 Basic Mouse Subsystem 92 Basic Video Subsystem 117 batch files 18 bimodal operation, device driver 348, 392 binary "raw" mode 74, 76, 78 putting keyboard in, with DosDevIOCtl Category 4, Function 51H 8182 putting keyboard in, with KbdGetStatus and KbdSetStatus 80 BIND utility 49, 6566 BIOS parameter block (BPB) 21, 195 structure of 360, 361 BKSCALLS.DLL file 84, 85 Block (DevHlp function) 386, 390, 66667 block device drivers 34950, 401 source code for header 354 BMSCALLS.DLL file 92 boot sector, disk 192, 19395 map of 193 BPB. See BIOS parameter block (BPB) BUFFERS directive 23 Build BPB command code 358, 36465 BVSCALLS.DLL file 117 C C1.EXE file, C2.EXE file, C3.EXE file 53 call gate 17, 302 C Compiler 41, 5356 switches and options 5456 CDLL.DLL dynamic link library 48285 CDLL.C source code 48283 CDLL.DEF module definition file 484 CINIT.ASM with initialization entry point 484 character(s) keyboard input of, with KbdCharIn and KbdStringIn 7475, 7677 video display 9596 character attribute 9596 controlling with DosWrite 98100 mapping of bytes in color text mode 107 mapping of bytes in monochrome text mode 107 character device drivers 34950, 401 data flow between application and 419 data flow between application and, when device monitor is registered 421 source code for header 354 character queue, management of, with DevHlp functions 388, 395 character set, ASCII and IBM extended 7079 child process 23236, 271 using anonymous pipes to redirect handles of 281, 28283 using DosExecPgm to run CHKDSK as asynchronous and synchronous 23436 using filters as 33946 child sessions 24042 CHKDSK program 197, 23436 C language programs calling API functions from 41 CDLL.C 48283 designing dynamic link libraries in 46466 DUMBTERM.C 25559 DUMP.C 14749 dynamic allocation of local storage 22425 dynamic linking of 460 FIND.C 337, 33839 lowercasing filter in 337 memory image after loading 36 memory model 30, 31 MOUDEMO.C8990 NEWEXE.H 727, 72836 PORTS.C 30910 PROTO.C 33435 registration of Ctrl-C signals 29899 SNAP.C 44552 TINYCMD.C 24447 using IOPL segments in 3056 WHEREIS.C 17579 writing dynamic link libraries in 48185 CL.EXE file 53, 54 client process, pipes 28485 CLOCK01.SYS file 23 clock/calendar chip 31718 clock timer utility using DosSleep 323 using periodic timer and system semaphore 32526 clusters, file 195 CMD.EXE file 1719 using DosStartSession to load 240, 24142 CODE directive in DEF files 32, 737, 738 code segment 28, 30, 31 new EXE header 72527 table describing 722 writable 22224 CodeView utility 50 color, video 99 color graphics adapter (CGA), mapping character attributes for 107 COM01.SYS, COM02.SYS files 119 COM1, COM2, and COM3 devices 119, 124, 125, 331 commands, accepted by command processor 1819 command code field, device driver request packet 356 command code functions, device driver 35876 COMMAND.COM file 1719 resident, initialization, and transient sections 1718 command line, Linker 5860 command line interface 10 command processor 1719 environment variables and 37, 38 sample TINYCMD 24354 CONFIG.SYS file loading, initialization 23 loading alternate device drivers 14, 347, 359, 418 loading Presentation Manager 19 setting IOPL status 303 setting multitasking options 242 context switch 227 counted string 715 CREF utility 49, 6162 Ctrl-C signals assembly language handler for 29798 C handler for 29899 cursor control with DosWrite 100102 with Vio 11012 D daemon processes 24 data area. See files area, disk DATA directive in DEF files 32, 737, 739 data segment 30, 31, 45 creating alias for, with DosCreateCSAlias 22224 new EXE header 72527 table describing 722 date. See time, date, and timers deadlock 145 (note) DEF files. See module definition (DEF) files deinstallation, device driver support for 349 DeInstall Driver command code 359, 370, 374 dependency statements 67 DeRegister (DevHlp function) 387, 393, 428, 667 DESCRIPTION directive in DEF files 737, 740 descriptor table, protected mode 6, 21112 DETACH command 113, 233 DevDone (DevHlp function) 357, 386, 390, 66768 DevHlp. See Device Helper (DevHlp) functions Device Close command code 359, 36970, 428 DEVICE directive 14, 24, 119, 192, 347 device driver(s) (.SYS) 1415, 347418 block type 34950 building 399403 character type 34950 command code functions 35876 disk 192 helper functions. (see Device Helper (DevHlp) functions) keyboard 8485 loading, initialization 23 loading optional 24 modes 35051 monitoring character drivers (see device monitors) mouse 89, 9192 pointer 91 printers, plotters, and modems 119 privilege levels 12, 13 processing a typical I/O request 39799 provided with OS/2 kernel 1415 samples block 40318 skeleton 37684 screen 11617 structure 35158 system layering and 11, 12 unique aspects of OS/2 34749 Device Helper (DevHlp) functions 348, 38497 ABIOS-related 389 categories of 38485, 38689 character queue management 388, 395 example of a call from 385 form 658 functions listed alphabetically 65860 functions listed numerically 66061 hardware management 386, 391 memory management 38687, 39192 miscellaneous services 389, 39697 monitor management 387, 39394 process management 386, 39091 reference 65793 request packet management 388, 39495 semaphore management 387, 394 timer management 388, 396 device monitors 349, 41955 API functions 420, 501 character data packet for PRN driver 426 code page packet for PRN driver 427 data packet for KBD$ driver 425 data packet for MOUSE$ driver 426 driver support for 428 keyboard 8586 organization of 42127 print spoolers as 122 sample monitor SNAP 42855 skeleton of code used in 42324 Device Open command code 358, 36970 DGROUP 30, 36, 4445 direct disk access 2025 API functions for 202 sample code for 2035 directories 131, 165 API functions for 167, 498 current (.) 199 format of single entry in 198 hierarchical (tree) structure 166 parent (..) 199 root 166 searching 16771 selecting, modifying 17273 discardable memory segments 22022 disk(s) 191205 Dos API functions for 498 direct access of 2025 distinct volume areas 191200 fixed disk partitions 2012 medium descriptor bytes for IBM-compatible 364 DISK01.SYS, DISK02.SYS file 23, 192 disk bootstrap program 20, 21, 22, 195 disk buffers 23, 145 disk drives API functions for manipulating 167 file system on logical 165, 166 obtain, change current 173 vs volumes 173 disk updates, forced 145 DLL. See dynamic link libraries DosAllocHuge (API function) 210, 220, 502 allocating shared memory segment with 287 flag word for 215 huge block allocation with 217, 21819 DosAllocSeg (API function) 210, 220, 224, 290, 503 allocating global memory segment with 214, 215, 216 allocating shared memory segment with 287 DosAllocShrSeg (API function) 210, 224, 503 creating named shareable segments with 286, 287 DosBeep (API function) 362, 452, 503 DosBufReset (API function) 132, 285, 504 forced disk updates using 145 DOSCALL1.DLL 16 DosCallback (API function) 504 using with IOPL segments 305 DosCallNmPipe (API function) 284, 5045 exchanging data with pipe's creator with 284 DosCaseMap (API function) 362, 505 DosChDir (API function) 167, 505 select current directory with 172 DosChgFilePtr (API function) 132, 362, 5056 direct disk access with 202 file access with 144 use with semaphores 273 DosCLIAccess (API function) 506 using with IOPL segments 305, 306 DosClose (API function) 132, 139, 167, 202, 281, 340, 362, 506 DosCloseQueue (API function) 289, 292, 294, 506 DosCloseSem (API function) 272, 507 closing semaphores with 279 DosConnectNmPipe (API function) 284, 507 managing named pipes with 284, 285 DosCreateCSAlias (API function) 210, 507 creating alias for data segments with 22224 DosCreateQueue (API function) 289, 292, 508 creating a queue with 28889 DosCreateSem (API function) 272, 394, 508 creating system semaphores with 275 DosCreateThread (API function) 232, 508 starting threads with 237 DosCwait (API function) 232, 240, 509 using with child processes 233, 281 DosDelete (API function) 132, 362, 509 deleting files with 146 DosDevConfig (API function) 40, 362, 50910 DosDevIOCtl (API function) 362, 510, 62156 Category 1 functions, configuring serial port 12628 Category 1 Function 41H, Set Baud Rate 127, 621 Category 1 Function 42H, Set Line Characteristics 127, 622 Category 1 Function 44H, Transmit Byte Immediate 127, 622 Category 1 Function 45H, Set Break Off 127, 622 Category 1 Function 46H, Set Modem Control Signals 127, 62223 Category 1 Function 47H, Stop Transmitting (as if XOFF received) 127, 623 Category 1 Function 48H, Start Transmitting (as if XON received) 127, 623 Category 1 Function 4BH, Set Break On 127, 623 Category 1 Function 53H, Set Device Control Block (DCB) 127, 624 Category 1 Function 61H, Get Baud Rate 127, 625 Category 1 Function 62H, Get Line Characteristics 127, 625 Category 1 Function 64H, Get COM Status 127, 625 Category 1 Function 65H, Get Transmit Data Status 127, 626 Category 1 Function 66H, Get Modem Output Control Signals 127, 626 Category 1 Function 67H, Get Modem Input Control Signals 127, 62627 Category 1 Function 68H, Get Number of Characters in Receive Queue 127, 627 Category 1 Function 69H, Get Number of Characters in Transmit Queue 127, 627 Category 1 Function 6DH, Get COM Error Information 127, 62728 Category 1 Function 72H, Get COM Event Information 127, 628 Category 1 Function 73H, Get Device Control Block (DCB) 127, 628 Category 3 (pointer device) Function 72H, Get Pointer-Draw Routine Address 629 Category 4 functions, for keyboard status and control 79 Category 4 Function 50H, Set Code Page 79, 629 Category 4 Function 51H, Set Input Mode 79, 629 putting keyboard into binary mode with 8182 Category 4 Function 52H, Set Interim Character Flags 79, 630 Category 4 Function 53H, Set Shift State 79, 630 Category 4 Function 54H, Set Typematic Rate and Delay 79, 631 Category 4 Function 55H, Notify Change of Foreground Session 79, 631 Category 4 Function 56H, Set Task Manager Hot Key 79, 63132 Category 4 Function 57H, Bind Logical Keyboard to Physical Keyboard 79, 632 Category 4 Function 58H, Set Code Page Identifier 79, 632 Category 4 Function 5CH, Set NLS and Custom Code Page 79, 632 Category 4 Function 5DH, Create Logical Keyboard 79, 633 Category 4 Function 5EH, Destroy Logical Keyboard 79, 633 Category 4 Function 71H, Get Input Mode 79, 633 Category 4 Function 72H, Get Interim Character Flags 79, 634 Category 4 Function 73H, Get Shift State 79, 634 Category 4 Function 74H, Read Character Data Records 79, 635 Category 4 Function 75H, Peek Character Data Record 79, 636 Category 4 Function 76H, Get Task Manager Hot Key79, 636 Category 4 Function 77H, Get Keyboard Type 79, 637 Category 4 Function 78H, Get Code Page ID 79, 637 Category 4 Function 79H, Translate Scan Code to ASCII 79, 637 Category 5 functions, for controlling printer 121 Category 5 Function 42H, Set Frame Control 121, 638 Category 5 Function 44H, Set Infinite Retry 121, 638 Category 5 Function 46H, Initialize Printer 121, 638 Category 5 Function 48H, Activate Font 121, 639 Category 5 Function 62H, Get Frame Control 121, 639 Category 5 Function 64H, Return Infinite Retry Mode 121, 639 Category 5 Function 66H, Return Printer Status 121, 640 Category 5 Function 69H, Query Active Font 121, 640 Category 5 Function 6AH, Verify Code Page and Font 121, 640 Category 7 functions, for mouse status and control 88, 92 Category 7 Function 50H, Enable Printer Drawing after Session Switch 88, 641 Category 7 Function 51H, Notify of Display Mode Change 88, 641 Category 7 Function 52H, Notify of Impending Session Switch 88, 642 Category 7 Function 53H, Set Mouse Scaling Factors 88, 642 Category 7 Function 54H, Set Mouse Event Mask 88, 642 Category 7 Function 55H, Set Mouse Button for System Hot Key 88, 643 Category 7 Function 56H, Set Mouse Pointer Shape 88, 643 Category 7 Function 57H, Cancel Exclusion Area and Draw Mouse Pointer 88, 643 Category 7 Function 58H, Set Exclusion Area for Mouse Pointer 88, 644 Category 7 Function 59H, Set Mouse Pointer Position 88, 644 Category 7 Function 5AH, Specify Address of Protected Mode Pointer-Draw Routine 88, 644 Category 7 Function 5BH, Specify Address of Real Mode Pointer-Draw Routine 88, 645 Category 7 Function 5CH, Set Mouse Driver Status 88, 645 Category 7 Function 60H, Get Number of Mouse Buttons 88, 645 Category 7 Function 61H, Get Number of Mickeys per Centimeter 88, 646 Category 7 Function 62H, Get Mouse Driver Status 88, 646 Category 7 Function 63H, Read Mouse Event Queue 88, 64647 Category 7 Function 64H, Get Mouse Event Queue Status 88, 647 Category 7 Function 65H, Get Mouse Driver Event Mask 88, 647 Category 7 Function 66H, Get Mouse Scaling Factors 88, 648 Category 7 Function 67H, Get Mouse Pointer Position 88, 648 Category 7 Function 68H, Get Mouse Pointer Shape 88, 648 Category 7 Function 69H, Get Mouse Button Used as System Hot Key 88, 649 Category 8 (logical disk) Function 00H, Lock Logical Drive 203, 649 Category 8 Function 01H, Unlock Logical Drive 203, 649 Category 8 Function 02H, Redetermine Media 650 Category 8 Function 03H, Set Logical Drive Map 373, 650 Category 8 Function 20H, Check if Block Device Removable 370, 650 Category 8 Function 21H, Get Logical Drive Map 373, 650 Category 8 Function 43H, Set Device Parameters 651 Category 8 Function 44H, Write Track 652 Category 8 Function 45H, Format and Verify Track 652 Category 8 Function 63H, Get Device Parameters 403, 653 Category 8 Function 64H, Read Track 653 Category 8 Function 65H, Verify Track 653 Category 9 (physical disk) Function 00H, Lock Physical Drive 374, 654 Category 9 Function 01H, Unlock Physical Drive 374, 654 Category 9 Function 44H, Write Physical Track 374, 654 Category 9 Function 63H, Get Physical Device Parameters 374, 654 Category 9 Function 64H, Read Physical Track 374, 655 Category 9 Function 65H, Verify Physical Track 374, 655 Category 10 (character device monitor) Function 40H, Register Monitor 655 Category 11 (general device control) Function 01H, Flush Input Buffer 369, 656 Category 11 Function 02H, Flush Output Buffer 369, 656 Category 11 Function 60H, Query Monitor Support 421, 656 DosDisConnectNmPipe (API function) 284, 511 breaking pipe connection with 284, 285 DosDupHandle (API function) 132, 285, 340, 511 DosEnterCritSec (API function) 232, 511 using with threads 237, 273 DosErrClass (API function) 512 DosError (API function) 42, 51213 DosExecPgm (API function) 35, 232, 340, 454, 51314 launching daemon process with 24 managing child processes with 23236, 281 DosExit (API function) 30, 42, 46, 69, 232, 51415 terminating threads with 237 DosExitCritSec (API function) 232, 515 using with threads 237 DosExitList (API function) 42, 232, 465, 515 registering ExitList procedures with 279 DosFileLocks (API function) 132, 516 locking files and records with 14546 DosFindClose (API function) 167, 169, 362, 516 DosFindFirst (API function) 167, 362, 51618 searching directories with 167, 168, 16971 DosFindNext (API function) 167, 362, 518 searching directories with 167, 168, 16971 DosFlagProcess (API function) 51819 sending event flag signals with 296 DosFreeModule (API function) 461, 462, 519 DosFreeSeg (API function) 210, 220, 290, 292, 519 giving up rights to shared memory segments with 288 releasing global memory block with 215, 216 DosFSRamSemClear (API function) 272, 519 managing fast-safe RAM semaphores with 27980 DosFSRamSemRequest (API function) 272, 520 managing fast-safe RAM semaphores with 27980 DosGetCollate (API function) 52021 DosGetCp (API function) 521 DosGetCtryInfo (API function) 40, 362, 52122 DosGetDateTime (API function) 40, 318, 52223 getting date and time with 321, 322 DosGetDBCSEv (API function) 362, 523 DosGetEnv (API function) 40, 362, 52324 DosGetHugeShift (API function) 210, 524 returning shift count in huge memory blocks with 217 DosGetInfoSeg (API function) 40, 318, 452, 52425 setting date and time with 319, 32021 DosGetMachineMode (API function) 40, 526 DosGetMessage (API function) 362, 526 DosGetModHandle (API function) 461, 463, 526 DosGetModName (API function) 461, 463, 527 DosGetPID (API function) 40, 232, 527 DosGetPPID (API function) 232, 527 DosGetProcAddr (API function) 461, 462, 52728 DosGetPrty (API function) 40, 232, 237, 528 DosGetResource (API function) 52829 DosGetSeg (API function) 210, 529 DosGetShrSeg (API function) 210, 529 obtaining valid selectors with 286 DosGetVersion (API function) 40, 52930 DosGiveSeg (API function) 210, 290, 530 giving memory segments with 287, 29092 DosHoldSignal (API function) 530 DosInsMessage (API function) 531 DosKillProcess (API function) 42, 232, 295, 531 terminate child process with 233 DosLoadModule (API function) 24, 46162, 531 DosLockSeg (API function) 210, 532 allocating discardable segments with 220, 22122 DosMakeNmPipe (API function) 284, 53233 creating a named pipe with 284 DosMakePipe (API function) 533 creating anonymous pipes with 28081 DosMemAvail (API function) 40, 210, 534 DosMkDir (API function) 167, 172, 534 DosMonClose (API function) 370, 420, 422, 428, 534 DosMonOpen (API function) 370, 420, 421, 422, 428, 452, 534 DosMonRead (API function) 420, 422, 452, 535 DosMonReg (API function) 420, 42122, 428, 452, 535 DosMonWrite (API function) 420, 422, 452, 536 DosMove (API function) 132, 167, 536 moving files with 146 renaming directories with 17273 DosMuxSemWait (API function) 272, 324, 53637 waits for semaphores to be cleared 277 DosNewSize (API function) 132, 537 DosOpen (API function) 132, 362, 53739 direct disk access with 202 obtaining handles for pipes with 284 opening files with 13339, 340 attribute parameter 136 efficient use 137, 13839 OpenFlag parameter 133, 134 OpenMode parameter 133, 134, 135 outcome of, based on sharing mode and access rights of requesting processes 136 printer output with 12021 searching files with 167 serial port I/O with 124, 125 video output with 9798 DosOpenQueue (API function) 289, 290, 539 DosOpenSem (API function) 272, 275, 394, 53940 DosPeekNmPipe (API function) 284, 540 DosPeekQueue (API function) 289, 294, 54041 DosPFSActivate (API function) 541 DosPFSCloseUser (API function) 541 DosPFSInit (API function) 542 DosPFSQueryAct (API function) 542 DosPFSVerifyFont (API function) 542 DosPhysicalDisk (API function) 543 DosPortAccess (API function) 305, 306, 54344 DosPTrace (API function) 232, 54446 DosPurgeQueue (API function) 289, 294, 546 DosPutMessage (API function) 362, 546 DosQAppType (API function) 54647 DosQCurDir (API function) 40, 167, 172, 362, 547 DosQCurDisk (API function) 40, 168, 173, 362, 547 DosQFHandState (API function) 132, 139, 285, 54748 DosQFileInfo (API function) 132, 139, 362, 54849 DosQFileMode (API function) 132, 139, 362, 549 DosQFSInfo (API function) 40, 167, 173, 174, 54950 DosQHandType (API function) 79, 108, 132, 139, 285, 330, 550 DosQNmPHandState (API function) 284, 55051 DosQNmPipeInfo (API function) 284, 551 DosQNmPipeSemState (API function) 284, 55152 DosQSysInfo (API function) 552 DosQueryQueue (API function) 289, 294, 552 DosQVerify (API function) 40, 553 DosR2StackRealloc (API function) 305, 553 DosRead (API function) 132, 362, 55354 direct disk access with 202 file access with 143 keyboard input with 7374, 77, 78, 79 obtaining cursor position with 1012 reading from pipes with 281, 282, 284 serial port input/output 124, 126 use with semaphores 273 DosReadAsync (API function) 132, 554 file access with 14344 reading from pipes with 28182, 284 DosReadQueue (API function) 289, 292, 55455 DosReallocHuge (API function) 210, 21920, 555 DosReallocSeg (API function) 210, 55556 allocating discardable segments with 220, 22122 resizing global memory with 215, 216 DosResumeThread (API function) 232, 237, 556 DosRmDir (API function) 167, 172, 556 DosScanEnv (API function) 40, 556 DosSearchPath (API function) 167, 171, 55657 DosSelectDisk (API function) 167, 173, 557 DosSelectSession (API function) 232, 241, 557 DosSemClear (API function) 272, 394, 422, 557 clearing semaphores with 277, 279 releasing thread's ownership of a semaphore with 27677 DosSemRequest (API function) 272, 27677, 394, 558 DosSemSet (API function) 272, 558 setting semaphores with 277, 278 use in a device monitor 422 DosSemSetWait (API function) 272, 277, 558 DosSemWait (API function) 272, 277, 324, 558 DosSendSignal (API function) 559 DosSetCp (API function) 559 DosSetDateTime (API function) 318, 321, 322, 55960 DosSetFHandState (API function) 132, 285, 560 DosSetFileInfo (API function) 132, 561 DosSetFileMode (API function) 132, 561 DosSetFSInfo (API function) 167, 173, 562 DosSetMaxFH (API function) 132, 562 DosSetNmPHandState (API function) 284, 562 DosSetNmPipeSem (API function) 284, 563 DosSetProcCp (API function) 563 DosSetPrty (API function) 232, 237, 56364 DosSetSession (API function) 232, 241, 564 DosSetSigHandler (API function) 42, 295, 296, 565 DosSetVec (API function) 42, 566 DosSetVerify (API function) 365, 566 DosSizeSeg (API function) 210, 56667 DosSleep (API function) 237, 318, 322, 323, 567 DosStartSession (API function) 232, 240, 24243, 56769 DosStopSession (API function) 232, 241, 569 DosSubAlloc (API function) 210, 225, 569 DosSubFree (API function) 210, 225, 56970 DosSubSet (API function) 210, 22425, 570 DosSuspendThread (API function) 232, 237, 570 DosTimerAsync (API function) 318, 324, 32526, 570 DosTimerStart (API function) 318, 324, 32526, 57071 DosTimerStop (API function) 318, 571 DosTransactNmPipe (API function) 284, 571 DosUnlockSeg (API function) 210, 220, 22122, 571 DosWaitNmPipe (API function) 284, 572 DosWrite (API function) 40, 46, 69, 132, 362, 572 clearing the screen, scrolling with 100 controlling character attributes with 98100 cursor control with 100102 direct disk access with 202 file access with 143 mode control with 102 printer output with 12021 print spooler and 12223, 124 serial port input/output with 124, 125, 126 use with semaphores 274 video output with 9394, 9698 writing to pipes with 281, 282, 284 DosWriteAsync (API function) 132, 57273 file access with 14344 writing to pipes with 28182, 284 DosWriteQueue (API function) 289, 290, 573 drivers. See device driver(s) (.SYS) DUMBTERM program 25469 DUMBTERM.ASM source code 128, 26068 DUMBTERM.C source code 128, 25559 DUMBTERM.DEF module definition file 269 DUMP utility program DUMP.ASM assembly source code 14956 external subroutine ARGC.ASM 15759 external subroutine ARGV.ASM 15962 DUMP.C source code 14749, 157 DUMP.DEF module definition file 162 sample output 163 dynamic link libraries (DLL) 9, 1617, 45785 API functions for 501 designing new 46466 dynamic linking at load time 45961 dynamic linking at run time 46163 fast-safe RAM semaphores used with 280 initialization 461, 46465 IOPl segments in 3012 provided with OS/2 kernel 1617 sample calling program SHOWARGS.EXE 47780 sample library ASMHELP.DLL 46976 steps in creating 485 syntax of module definition files used for 465, 466 using import libraries 481 writing, in C 48185 writing, in MASM 46768, 469 E enhanced graphics adapter (EGA), mapping character attributes for 107 entry table, defining EXE file entry points 72425 environment block 37 in child and parent processes 234 hex and ASCII dump of program's 39 for protected mode session 38 EOI (DevHlp function) 386, 668 error codes, OS/2 697706 status word field 357 escape sequences, ANSI 98 positioning cursor with 101 selecting reverse video attribute with 99100 event flags 295, 296 EXEC function 232 EXECSORT program 34046 EXECSORT.ASM source code 34145 EXECSORT.DEF module definition file 346 EXETYPE directive in DEF files 737, 740 executable (EXE) program files 3435. See also application program(s); segmented executable file, components of ExitList procedures 279, 280 EXPORTS directive in DEF files 32, 306, 315, 481, 737, 74041 extended character set 7079 extended keys 74 external commands 18 extrn directive 44 F Family API (FAPI) 39 family applications 27 far attribute 30, 41, 44, 45, 460, 481 far pointers 5 fast-safe RAM semaphores 274, 276 use of 27980 FAT. See file allocation table file allocation table (FAT) 192, 19597 compatibility with future versions of OS/2 131 dump of first block of 196 file management 13163. See also directories; disk(s); disk drives; volume(s) API functions for 132, 498 creating, opening, and closing files 13339 file and record locking 14546 file finder utility 17590 forcing disk updates 145 handles and filenames 13233 reading and writing files 13944 renaming and deleting files 146 sample programs DUMP.ASM and DUMP.C for displaying file contents 14763 File Manager (Presentation Manager) 19 filename(s) 38 extensions 5051, 133 handles and 13233 hex and ASCII dump containing 39 file pointer 140 files area, disk 192, 199200 filters 32946 building 33139 keyboard input with DosRead 78 operation of 331 system support for 32930 using, as child processes 33946 FIND.C filter program 33839 FIND filter 329, 337 fixed disk partitions 201, 202 Flush Input Buffers command code 358, 36869 Flush Output Buffers command code 358, 36869 FORMAT program 197 free() function 224 FreeLIDEntry (DevHlp function) 374, 389, 669 FreePhys (DevHlp function) 374, 386, 391, 669 FreeReqPacket (DevHlp function) 388, 39495, 66970 G GDT. See global descriptor table Generic IOCtl command code 359, 37172, 428 GetDOSVar (DevHlp function) 389, 39091, 396, 670 Get Fixed Disk Map command code 359, 37576 GetLIDEntry (DevHlp function) 389, 671 Get Logical Drive Map command code 359, 373 global descriptor table (GDT) 22, 212 global heap 209 global information segment, setting date and time with 31921 global initialization 461, 465 global memory 21416 glossary 74760 graphical user interface. See Presentation Manager graphics (all-points-addressable) mode 95, 96 group directive 30, 44 H handles filenames and 13233 pipe vs file 282 using anonymous pipes to redirect child process's 281, 28283 hardware management, with DevHlp functions 386, 391 header, device driver 351, 352 attribute word 352, 353 source code for block and character 354 header, EXE files 34 new 720, 721 old 718, 719 HEAPSIZE directive in DEF files 32, 737, 741 HELLO program (HELLO.EXE) example using programming tools 6770 HELLO.ASM source file 42, 4344, 4546, 52 HELLO.DEF module definition file 46, 47 HELLO.MAP map file produced by Linker 57 HELLO.OBJ object file 52, 57 HELLO.REF ASCII text file 6162 high level language (HLL) naming conventions 30, 31 huge memory blocks 21720 I IBM extended character set 7079 identification field, disk boot sector 195 IDT. See interrupt descriptor table idle-time thread category 23738 IMPLIB utility 32, 49, 481 imported names table 724 import libraries 32 vs dynlink libraries 459 using with dynlink libraries 481 IMPORTS .DEF file directive 32, 737, 74142 Init (Initialization) command code 358, 35962 API calls used during operation of 362 BIOS parameter block (BPB) 360, 361 request packet format 360 init mode, device driver 350, 351 input mode 74 input/output (I/O) 39799. See also device driver(s); IOPL (I/O privilege); keyboard input; mouse, input; See also parallel ports; printer; redirectable I/O; request packets, I/O; serial ports; video display Input Status command code 358, 368 installable file systems 10 instance initialization 461, 465 Intel 80286 microprocessor 3, 21013 Intel 80386 microprocessor 3 inter-driver communication (IDC) points 348 internal commands 18 internationalization, API functions supporting 501 interprocess communication (IPC) 89, 27199 anonymous pipes 28083 named pipes 28386 queues 28894 semaphores 27280 shared memory 28688 signals 29599 interrupt descriptor table (IDT) 22 interrupt mode, device driver 350 interrupt routine, device driver adding interrupt handling 4012 basic task of 35758 custom drivers 399400 interrupt time (asynchronous), device driver 351 interrupt vectors 391 IOPL (I/O privilege) 30116 API functions and 303, 304, 305 description of 302, 303 in dynlink libraries 457 sample application PORTS 30716 using in OS/2 applications 3056 IPC. See interprocess communication K KBD$ device driver 84, 85 device monitor for 420, 425 KBD01.SYS file 23, 84 Kbd API functions, keyboard input with 74, 75, 7677 KBDCALLS.DLL 16, 84, 85 KbdCharIn (API function) 57374 keyboard input with 7475, 76 KbdClose (API function) 75, 575 KbdDeRegister (API function) 75, 85, 575 KbdFlushBuffer (API function) 75, 575 KbdFreeFocus (API function) 75, 84, 575 KbdGetCp (API function) 75, 576 KbdGetFocus (API function) 75, 83, 576 KbdGetStatus (API function) 75, 57677 putting keyboard into binary mode with 80 KbdOpen (API function) 75, 577 creating/using logical keyboard with 8384 KbdPeek (API function) 57879 keyboard input with 75 KbdRegister (API function) 75, 84, 57980 KbdSetCp (API function) 75, 580 KbdSetCustXt (API function) 75, 58081 KbdSetStatus (API function) 75, 58182 putting keyboard into binary mode with 80 KbdStringIn (API function) 582 keyboard input with 7576, 77 KbdSynch (API function) 75, 583 KbdXlate (API function) 75, 58385 kernel 5. See also Application Program Interface (API) functions device drivers provided with 1415 device monitor dispatcher 420 dynlink libraries provided with 1617 memory allocation by 209 privilege levels 12, 13, 228 services offered by 1516 system layering and, 11, 12 kernel applications 2728. See also application program(s) kernel mode 228 device driver 350 keyboard monitors 8586 router 84 status and control 7982 subsystems and drivers 8485 keyboard input logical keyboards 8284 methods and modes 7374 with DosRead 7779 with Kbd functions 7477 L LAN Manager 271, 283 late binding 9 LDT. See local descriptor table leave instruction 30 LIBRARY .DEF file directive 32, 737, 742 Library Manager (LIB) utility 49, 6265 operations 6364 switches and options 6465 table of contents produced by 63 Linker 9, 34, 5660 map produced by, for HELLO program 57 switches accepted by 5960 load module format components of segmented executable file 71527 NEWEXE.H header file 727, 72836 loadtime dynamic linking 45961 local descriptor table (LDT) 17, 212 local heap 224 local storage 22425 Lock (DevHlp function) 387, 392, 67172 logical drives 16566. See also directories; volume(s) logical keyboards 8284 logical video buffer (LVB) 94 logical volumes. See volume(s) LPT1 (PRN), LPT2, and LPT3 devices 119, 120, 331 LVB. See logical video buffer M Macro Assembler (MASM) 5153. See also assembly language programs designing dynamic link libraries in 46466 switches and options 5253 writing dynamic link libraries in 46769 mailslots 271 make files 6667 MAKE utility 49, 6667 malloc() function 224 MARKEXE utility 49 MASM.EXE file 51. See also Macro Assembler (MASM) MAXWAIT directive 242 Media Check command code 358, 36364 MEMMAN directive 214 memory addressing 392 memory management 20925. See also memory segments; shared memory API functions for 210, 49899 with DevHlp functions 38687, 39192 discardable memory segments 22022 global memory 21416 huge memory blocks 21720 local storage 22425 memory protection 67, 21013, 228 virtual memory 21314 writable code segments 22224 Memory Manager 7 memory map image of small model MASM or C program after loading 36 supporting protected and real modes 25 supporting protected mode only 26 memory models 3031 memory overcommit calculation 36 memory protection 67, 21013, 228. See also protected mode; real mode memory segments. See also code segment; data segment; memory management discardable 22022 global 21416 logically contiguous storage spanning multiple 21720 memory models and 30, 31 registers 6 segment table 722 shareable, named 89, 286 swapping 7, 21314 Microsoft Corporation 3 operating systems from 4 modes ASCII and binary 74, 76, 78, 79, 81, 82 device driver 35051 keyboard 7374 multitasking kernel and user 228 protected (see protected mode) real (see real mode) video 95102 module(s), system loader 45859 format 71536 module definition (DEF) files 3233 ASMHELP.DEF 476 CDLL.DEF 484 directives in 73746 DUMBTERM.DEF 269 DUMP.DEF 162 EXECSORT.DEF 346 HELLO.DEF 46, 47 for MASM dynamic link library 469 PORTS.DEF 315, 316 SHOWARGS.DEF 480 SNAP.DEF 455 syntax 33, 465, 466, 73746 TEMPLATE.DEF 383 TINYCMD.DEF 254 TINYDISK.DEF 417 WHEREIS.DEF 190 module reference table 460, 724 MONCALLS.DLL 16 MonFlush (DevHlp function) 387, 393, 394, 672 MonitorCreate (DevHlp function) 387, 393, 428, 67273 monitor dispatcher 420 monitor management, with DevHlp functions 387, 39394. See also video display monochrome display adapter (MDA), mapping character attribute bytes for 107 MonWrite (DevHlp function) 387, 393, 428, 67374 MORE filter 329 MOUCALLS.DLL 17, 92 MouClose (API function) 86, 88, 585 MOUDEMO.C program 8990 MouDeRegister (API function) 87, 92, 585 MouDrawPtr (API function) 86, 87, 585 MouFlushQue (API function) 86, 585 MouGetDevStatus (API function) 87, 586 MouGetEventMask (API function) 87, 586 MouGetHotKey (API function) 87, 587 MouGetNumButtons (API function) 87, 587 MouGetNumMickeys (API function) 87, 587 MouGetNumQueEl (API function) 86, 87, 58788 MouGetPtrPos (API function) 86, 87, 588 MouGetPtrShape (API function) 86, 58889 MouGetScaleFact (API function) 87, 589 MouInitReal (API function) 87, 589 MouOpen (API function) 86, 87, 58990 MouReadEventQue (API function) 86, 87, 590 MouRegister (API function) 87, 92, 59192 MouRemovePtr (API function) 86, 592 mouse API functions 8687 demonstration program for Mou API 8990 input 8690 router 92 subsystems and drivers 9192 MOUSE$ driver 91, 92 device monitor for 420, 426 mode limitations 89 MouSetDevStatus (API function) 87, 593 MouSetEventMask (API function) 87, 593 MouSetHotKey (API function) 87, 59394 MouSetPtrPos (API function) 86, 594 MouSetPtrShape (API function) 86, 59495 MouSetScaleFact (API function) 87, 595 MouSynch (API function) 87, 59596 MS-DOS operating system 3, 4 compatibility with OS/2 10 device drivers under 347, 348 MS-DOS stub program 720 MSG.DLL 16 multitasking 78, 22769. See also interprocess communication (IPC) API functions for 23143 managing processes 23236 managing sessions 24042 managing threads 23739 configuring OS/2 multitasker 24243 device drivers and 348 file and record locking 14546 in OS/2 22831 sample command processor TINYCMD 24354 sample multithreaded application DUMBTERM 25469 multithreading DUMBTERM application demonstrating 25469 source code for demonstration of 23839 N NAME directive in DEF files 32, 737, 743 named pipes 28386 API functions for 284 state transitions for 285 near attribute 30, 45 NEWEXE.H header file 727, 72836 NLS.DLL 17 Nondestructive Read command code 358, 367 nonresident names table, 725 O object module libraries 32, 34, 62. See also import libraries; Library Manager (LIB) utility description of 34 vs dynlink libraries 459 OLD directive in DEF files 737, 743 ordinals, specifying dynlink libraries with 460 OS2KRNL 2122, 23 OS2LDR 21, 22 OS2.LIB 65, 69 OS/2 operating system 3 bypassing, and directly accessing adapter I/O ports 3012 compatibility with MS-DOS 10 kernel applications (see application program(s)) key features 410 loading, initialization 2026 multitasking in 22831 (see also multitasking) programming tools (see programming tools) structure 1220 (see also command processor; device driver(s) (.SYS); dynamic link libraries (DLL); kernel; Presentation Manager) system layers 11, 12, 13 unique features of device drivers under 34749 Output Status command code 358, 368 P page directive 44 paragraph address 6 parallel ports 119 parent directory 199 parent process 23234, 271 parent sessions 24042 Partitionable Fixed Disk command code 359, 37475 partitions, fixed disk 201, 202 pascal keyword 41, 460, 481 path 165 pathnames 13233, 146, 172 child process 233 of processes in child sessions 240 Physical Memory Manager (PMM) 22 physical video buffer 94 PhysToGDTSelector (DevHlp function) 387, 392, 67475 PhysToUVirt (DevHlp function) 385, 387, 392, 675 PhysToVirt (DevHlp function) 385, 387, 392, 393, 418, 67576 pipes 8, 271 anonymous 28083 API functions for managing 284, 500 named 28386 pointer, driver for 91 POINTER$ driver 91, 92 pop-up support, video display 11315 PORTS application program 30716 output 316 PORTIO.ASM source code 3079 PORTS.ASM source code 31115 PORTS.C source code 30910 PORTS.DEF module definition file 315, 316 preemptive multitasking. See multitasking Presentation Manager 5, 1920. See also Application Program Interface (API) functions video display and 93 Presentation Manager applications 28 PRINT01.SYS, PRINT02.SYS files 23, 119 printer 119 print spooler 12224 sending output to 12021 print process 45 PRIORITY directive 243 privilege levels. See also IOPL (I/O privilege) application programs 67, 13, 3023 multitasking and 228 system components 12 PRN device driver 119, 330, 420 device monitor character data packet for 426 device monitor code packet for 427 process(es) 78, 37. See also child process; parent process API functions 40, 499 communication between threads and (see interprocess communication (IPC)) execution of 3941 management of, with DevHlp functions 386, 39091 multitasking 228, 229 API functions for 23236 pipe server/client 28485 process-specific information maintained by OS/2 230 registration of a signal handler by 295 termination 42 programmable interrupt controller (PIC) 391 programmable timers 318, 32226 programming tools 4970 BIND utility 6566 C Compiler 5356 CREF utility 6162 example using 6770 file types 5051 Library Manager 6265 Linker 5660 Macro Assembler 5153 MAKE utility 6667 Program Starter (Presentation Manager) 19 protected mode 6, 21113. See also privilege levels default command processor CMD.EXE 1719 environment block for 38 established by SysInit 22 memory maps showing support for 25, 26 physical address generation in 213 register contents at entry to applications in 37 structure of data and text descriptors 212 structure of selectors 211 PROTECTONLY directive 303 PROTMODE directive in DEF files 737, 744 PROTO filter template program 33139 assembly translation routine to create lowercasing filter 336 C translation routine to create lowercasing filter 337 PROTO.ASM source code 33134 PROTO.C source code 33435 PROTSHELL directive 19, 24 ProtToReal (DevHlp function) 389, 396, 677 PullParticular (DevHlp function) 388, 394, 677 PullReqPacket (DevHlp function) 388, 394, 678 push immediate instruction 44 PushReqPacket (DevHlp function) 388, 394, 678 Q QUECALLS.DLL 17 queue(s) 9, 271, 28894 API functions 289, 500 character (see character queue, management of, with DevHlp functions) characteristics of 288 code for opening, writing messages into, closing 29092 device driver request 356 inspecting, removing messages in 292, 29394 message placed in, upon termination of child sessions 240 writing records to 28990 QueueFlush (DevHlp function) 388, 395, 67879 QueueInit (DevHlp function) 388, 395, 679 queue owner 289, 292 QueueRead (DevHlp function) 388, 395, 67980 QueueWrite (DevHlp function) 388, 395, 680 R RAM (random access memory) 209. See also memory management RAM semaphores 274, 278 random access, defined 140 Read command code 358, 36567, 395 reading, defined 140 reading resources 71113 realloc() function 224 real mode 6, 210 default command processor COMMAND.COM 1719 memory map showing support for 25 physical address generation in 211 serial port and applications in 128 REALMODE directive in DEF files 737, 744 RealToProt (DevHlp function) 389, 396, 68081 record locking 14546 redirectable I/O directives 32930 for a filter run as a child process 33946 refresh (regen) buffer 95, 96 registers, contents of, at entry to protected mode application 37 Register (DevHlp function) 387, 393, 428, 681 RegisterStackUsage (DevHlp function) 386, 391, 682 regular thread category 23738 relocatable object modules 34, 56. See also Linker; object module libraries Removable Media command code 359, 37071 request packets, I/O, 15, 35557 command code field 356 format Build BPB function 365 Device Open, Device Close functions 369 Flush Input Buffers, Flush Output Buffers functions 368 Generic IOCtl functions 372 Get Fixed Disk/Logical Unit Map function 376 Get Logical Drive, Set Logical Drive 373 Init function 360 Input Status, Output Status functions 368 Media Check function 363 Nondestructive Read function 367 Partitionable Fixed Disk 375 Read, Write, Write with Verify functions 366 Removable Media, Reset Media, Deinstall Driver functions 370 input/output processing and 397 management of, with DevHlp functions 388, 39495 print spooler "write" 12223 prototypal device driver 355 status word field 356, 357 request queue, device driver 356 reserved area, disk 192, 195 Reserved command code 358, 359 Reset Media command code 359, 370, 372 ResetTimer (DevHlp function) 388, 396, 683 resident names table 724 Resource Compiler (RC) utility 50, 67 resource table, end-of-file resources 72324 response file 58 reverse video, using escape sequence to select 99100 ring buffer 395 rings. See privilege levels ROM ABIOS (advanced basic I/O system) 15 management of, with DevHlp functions 389 ROM BIOS, device driver support for 349 ROM bootstrap program 20, 21 ROMCritSection (DevHlp function) 389, 396, 397, 683 root directory 166, 192, 19798 Run (DevHlp function) 386, 390, 68384 runtime dynamic linking API functions for 461 sample code sequence for 46263 S SchedClockAddr (DevHlp function) 388, 396, 684 scheduler 7, 37 event-driven 227 preemptive 22728 screen. See also video display clearing, and scrolling with DosWrite 100 clearing, and scrolling with Vio 108, 109 SCREEN$ driver 116 SCREEN01.SYS, SCREEN02.SYS file 23, 116 screen group 8, 229. See also session(s) scrolling with DosWrite 100 with Vio 108, 109 search handle 168 security 301, 303 seek operation 140 segment. See memory segments segmented executable file block diagram of a file 716 code and data segments 72527 components of 71527 entry table 72425 header 720, 721 imported names table 724 module reference table, 724 MS-DOS stub program 720 nonresident names table 725 old EXE header 718, 719 resident names table 724 resource table 72324 segment table 722 simple example of 71718 Segmented Executable Linker (LINK.EXE). See Linker SEGMENTS directive in DEF files 306, 315, 737, 74445 selectors 6 getting/giving, in shared memory 286, 28788 structure of protected mode 211 semaphores 8, 271, 27280. See also system semaphores API functions for 272, 500 asynchronous read/write and 14344 fast-safe RAM 27980 management of, with DevHlp functions 387, 394 mutual exclusion with 27677 signaling with 27779 types of OS/2 27476 SemClear (DevHlp function) 387, 394, 68485 SemHandle (DevHlp function) 387, 685 SemRequest (DevHlp function) 387, 394, 686 SendEvent (DevHlp function) 389, 396, 68687 sequential access defined 140 sample code for 14143 serially reusable resource (SRR) 27273 serial ports 119 configuring 12628 input and output 12426 real mode applications and 128 server process, for pipes 28485 session(s) 78, 228, 229 API functions for managing 232, 24042, 499 using DosStartSession to load CMD.EXE 24142 SetIRQ (DevHlp function) 386, 391, 68788 Set Logical Drive Map command code 359, 373 SetROMVector (DevHlp function) 389, 396, 397, 688 SetTimer (DevHlp function) 388, 396, 68889 shared memory 89, 271, 28688 SHOWARGS program 47780 SHOWARGS.ASM source code 47780 SHOWARGS.DEF module definition file 480 SIGBREAK signal 295 SIGBROKENPIPE signal 295 SIGINTR signal 295 signals 9, 271, 29599 API functions for 500 OS/2, in the first class 295 registration of C handler for Ctrl-C 29899 registration of MASM handler for Ctrl-C 29798 SIGTERM signal 295 SLIBC.LIB, table of contents of 62, 63 SNAP device monitor 42855 building 45455 flow of control 453 operation of 45254 SNAP.ASM source code 42944 SNAP.C source code 44552 SNAP.DEF module definition file 455 using the program 455 SORT filter 329, 330 SortReqPacket (DevHlp function) 388, 394, 689 source file structure 2831 space character 58 spooler, print 12224 data flow with spooler loaded 124 data flow with spooler not loaded 123 STACKSIZE directive in DEF files 32, 737, 745 standard devices auxiliary device 120 command syntax for redirecting handles of 330 error device 133, 329 filter operation and 32930 input device 77, 133, 329 list device 120 output device 133, 329 STARTUP.CMD file 24 status word field, device driver request packet error codes used in 357 format of 356 strategy routine, device driver 35557, 399 functions selected by command code field 35859 I/O request processing 39798 STUB directive in DEF files 737, 746 stub program 720 swapper 213 switches C Compiler 5456 for compiling dynlink libraries 482 Library Manager 6465 Linker 5960 Macro Assembler 5253 synchronous access defined 140 sample code for 14143 SysInit (system initialization module) 2224 system loader 3537 module 45859 module format 71536 system memory arena 22 system modules keyboard 8485 mouse 9192 video 116, 117 system semaphores 27475 closing 279 code for creating or opening 27576 code for opening and setting 278 timer-process communication using 324, 32526 T targetfile and sourcefile 67 Task Manager 19, 229, 240 task state segment (TSS) 22, 305 task time (synchronous), device driver 351 TCYield (DevHlp function) 386, 390, 68990 TEMPLATE.SYS device driver 37684 TEMPLATE.ASM source code 37783 TEMPLATE.DEF module definition file 383, 384 terminal emulator. See DUMBTERM program text, writing to video display using DosWrite 9697 text (alphanumeric) mode description of 9596 mapping attribute bytes for monochrome or color 107 thread(s) 78, 228, 229, 23031 API functions for 232, 23739, 499 communication between processes and (see interprocess communication (IPC)) DosReadAsync and DosWriteAsync use of 144 multiple, using the same file handle 144 primary (thread 1) 230, 296 priorities of 23738 SNAP program threads of execution 45254 source code for multithreading demonstration 23839 thread-specific information maintained by OS/2 230 THREADS directive 230, 242 TickCount (DevHlp function) 388, 396, 690 time, date, and timers 31726 API functions for 318, 501 clock device 326 programmed delays 32223 reading and setting date and time 31922 timer management with DevHlp functions 388, 396 using timers 32426 time-critical thread category 23738 timer tick 7, 317 TIMESLICE directive 243 time-slicing, 7, 228, 317. See also multitasking TINYCMD command processor 24354 TINYCMD.ASM source code 24754 TINYCMD.C source code 24447 TINYCMD.DEF module definition file 254 TINYDISK device driver 40318 TINYDISK.ASM source code 40417 TINYDISK.DEF module definition file 417 traps, hardware 212, 214 TSS. See task state segment turnaround characater 74 U UNIX. See XENIX/UNIX operating system Unlock (DevHlp function) 387, 392, 69091 UnPhysToVirt (DevHlp function) 387, 392, 393, 691 UnSetIRQ (DevHlp function) 374, 386, 391, 69192 updates to disk, forcing 145 user interface. See Presentation Manager user mode device driver 350, 351 privilege levels 228 utilities. See programming tools V VerifyAccess (DevHlp function) 387, 392, 692 VERIFY command 365 video display 93117 modes 95102, 11213 monitor management with DevHlp functions 387, 39394 subsystems and drivers 11617 Vio functions for output 10215 video graphics array (VGA), mapping character attribute bytes for 107 VioAssociate (API function) 104, 596 VIOCALLS.DLL file 17, 117 VioCreateLogFont (API function) 104, 59697 VioCreatePS (API function) 104, 59798 VioDeleteSetID (API function) 104, 598 VioDeRegister (API function) 104, 117, 598 VioDestroyPS (API function) 104, 598 VioEndPopUp (API function) 104, 113, 11415, 598 Vio functions, video display with 94, 10215 VioGetAnsi (API function) 98, 103, 113, 599 VioGetBuf (API function) 103, 599 VioGetConfig (API function) 40, 103, 599600 VioGetCp (API function) 103, 600 VioGetCurPos (API function) 103, 110, 111, 600 VioGetCurType (API function) 103, 110, 11112, 600601 VioGetDeviceCellSize (API function) 104, 601 VioGetFont (API function) 103, 601 VioGetMode (API function) 40, 103, 602 getting screen vertical resolution and text lines with 11112 video mode control with 112, 113 VioGetOrg (API function) 104, 6023 VioGetPhysBuf (API function) 103, 603 VioGetState (API function) 40, 103, 6034 VioModeUndo (API function) 104, 604 VioModeWait (API function) 104, 6045 VioPopUp (API function) 104, 113, 11415, 233, 605 VioPrtSc (API function) 104, 606 VioPrtScToggle (API function) 104, 606 VioQueryFonts (API function) 104, 606 VioQuerySetIds (API function) 104, 607 VioReadCellStr (API function) 103, 106, 115, 607 VioReadCharStr (API function) 103, 607 VioRegister (API function) 104, 117, 60810 VioSavRedrawUndo (API function) 104, 610 VioSavRedrawWait (API function) 104, 61011 VioScrLock (API function) 104, 611 VioScrollDn, VioScrollLf, VioScrollRt, VioScrollUp (API function) 103, 108, 109, 61112 VioScrUnLock (API function) 104, 612 VioSetAnsi (API function) 98, 103, 612 VioSetCp (API function) 103, 61213 VioSetCurPos (API function) 103, 110, 613 VioSetCurType (API function) 103, 110, 11112, 613 VioSetDeviceCellSize (API function) 104, 613 VioSetFont (API function) 103, 614 VioSetMode (API function) 103, 112, 113, 61415 VioSetOrg (API function) 104, 616 VioSetState (API function) 103, 616 VioShowBuf (API function) 104, 617 VioShowPS (API function) 104, 617 VioWrtCellStr (API function) 103, 1056, 115, 617 VioWrtCharStr (API function) 103, 1056, 618 VioWrtCharStrAtt (API function) 103, 105, 106, 618 VioWrtNAttr (API function) 103, 108, 618 VioWrtNCell (API function) 103, 108, 619 VioWrtNChar (API function) 103, 108, 619 VioWrtTTY (API function) 102, 103, 105, 619 VirtToPhys (DevHlp function) 387, 392, 693 virtual devices 8 virtual memory 67, 21314 virtual memory manager (VMM) 22, 21314 VMM. See virtual memory manager volume(s) 31 vs disk drives 173 locking/unlocking logical 203 logical sectors 191, 192 volume labels 17374 API functions for manipulating 167 obtaining and displaying, with DosQFSInfo 174 W WHEREIS file finder utility program 17590 subroutines 189 WHEREIS.ASM source code 17989 WHEREIS.C source code 17579 WHEREIS.DEF module definition file 190 wildcards 171 window procedure 28 work queue 356. See also request queue, device driver(s) (.SYS) Write (Output) command code 358, 36567, 395 Write with Verify command code 358, 36567 writing, defined 140 X XENIX/UNIX operating system 3, 8 Y Yield (DevHlp function) 386, 390, 693ـ䁌ᅄ②鍌󲡻šƀߌɭ—ݹϛc7A$'BMOD!sotsrid)mo|bhi-5+NM@*01122334#vspV22XVUY?mrnhh|r>rzMOSXcfk k