home *** CD-ROM | disk | FTP | other *** search
Text File | 2013-11-08 | 1.8 MB | 42,813 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
- Microsoft Systems Journal Volume 3
-
- ────────────────────────────────────────────────────────────────────────────
-
- Vol. 3 No. 1 Table of Contents
-
- ────────────────────────────────────────────────────────────────────────────
-
- Preparing for Presentation Manager: Paradox(R) Steps Up to Windows 2.0
-
- Ansa Software is developing versions of the Paradox data base product for
- the 80386, OS/2, UNIX(R)/XENIX(R), and Microsoft(R) Windows environments,
- all largely sharing the same code. This article explains how Ansa ported
- their large MS-DOS(R) application to the Windows environment for one of
- the new versions.
-
-
- Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager
-
- Microsoft OS/2 Presentation Manager offers the best of the Microsoft Windows
- operating environment and the protected-mode OS/2 operating system. The
- sample program SayWhat highlights the similarities and differences between
- Presentation Manager and Windows programming techniques.
-
-
- Programming Considerations In Porting To Microsoft(R) XENIX(R) System
- V/386
-
- XENIX System V/386, Microsoft's version of UNIX for the 386, supports
- virtual memory, demand paging, and both 286 and 386 mode execution. This
- article outlines considerations that help programmers choose between 16- and
- 32-bit program models when developing applications for XENIX V/386.
-
-
- HEXCALC: An Instructive Pop-Up Calculator For Microsoft(R) Windows
-
- HEXCALC, a pop-up window hexadecimal calculator, explores a method of using
- dialog box templates to define the layout of child window controls on a
- program's main window, a technique illustrating features of both Microsoft
- Windows Version 2.0 and the OS/2 Presentation Manager.
-
-
- Effectively Using Far and Huge Data Pointers In Your Microsoft(R)
- C Programs
-
- Programs that use "huge" pointers to access very large amounts of data are
- often affected by the expense of using relatively slow 32-bit arithmetic.
- Under certain conditions, huge pointers can be converted to far pointers,
- resulting in a significant decrease in access time.
-
- EMS Support Improves Microsoft(R) Windows 2.0 Application Performance
-
- Windows 2.0 uses expanded memory to bank-switch Windows applications,
- allowing multiple large programs to run simultaneously as if each were
- the only application running.The article examines how Windows' exploitation
- of LIM 4.0 expanded memory support can affect your Windows application.
-
-
- EDITOR'S NOTE
-
- There has been much speculation about the amount of effort required to move
- applications from the Windows 2.0 environment to the OS/2 Presentation
- Manager. With varying estimates and opinions ringing in our ears, we decided
- to devote a major part of this issue to exploring what's involved in
- programming for the Presentation Manager.
-
- We turned to Michael Geary, who spent a great deal of the last year
- exploring Presentation Manager. He provides us with a step-by-step guide to
- moving a Windows 2.0 application to Presentation Manager. To illustrate the
- differences, we have includeded side-by-side the complete code listings of
- both versions of his tutorial program.
-
- Charles Petzold, inveterate Windows expert, also provides insight on the
- topic with parallel Microsoft(R) Windows 2.0 and Presentation Manager
- versions of his instructive HEXCALC program.
-
- Ansa Software has had notable success with the MS-DOS(R) version of its
- Paradox(R) database. Currently under development are Paradox versions for
- Microsoft Windows, OS/2 Presentation Manager, UNIX(R) 5.3 and XENIX(R),
- as well as protected-mode 80386. We talked with Richard Schwartz, vice
- president of software development and cofounder of Ansa, to discover how one
- software firm is preparing for their move to Presentation Manager.
-
- With all the hoopla surrounding the OS/2 systems, we don't want to forget
- the importance of recent XENIX and MS-DOS developments. In particular, the
- new version of XENIX for the 386 environments has generated much interest,
- including questions on the portability between different versions of XENIX.
- Martin Dunsmuir, who is responsible for XENIX development at Microsoft,
- answers these questions and others.
-
- We look forward to 1988, and the new developments this coming year will
- bring. Look for MSJ to be there with the details.──ED.
-
-
- Masthead
-
- JONATHAN D. LAZARUS
- Editor and Publisher
-
- EDITORIAL
-
- TONY RIZZO
- Technical Editor
-
- CHRISTINA G.DYAR
- Associate Editor
-
- JOANNE STEINHART
- Production Editor
-
- GERALD CARNEY
- Staff Editor
-
- KIM HOROWITZ
- Editorial Assistant
-
- ART
-
- MICHAEL LONGACRE
- Art Director
-
- VALERIE MYERS
- Associate Art Director
-
- CIRCULATION
-
- WILLIAM B. GRANBERG
- Circulation Manager
-
- L. PERRIN TOMICH
- Assistant to the Publisher
-
- DONNA PUIZINA
- Administrative Assistant
-
- Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
- in part or in whole without permission is prohibited.
-
- Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
- NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
- Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
- President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
- William Neukom, Secretary.
-
- Microsoft Corporation assumes no liability for any damages resulting from
- the use of the information contained herein.
-
- Microsoft, MS-DOS, XENIX, CodeView and the Microsoft logo are registered
- trademarks of Microsoft Corporation. PageMaker is a registered trademark of
- Aldus Corporation. Paradox is a registered trademark of Ansa Software, a
- Borland Company. Apple and Macintosh are registered trademarks of Apple
- Computer, Inc. UNIX is a registered trademark of American Telephone and
- Telegraph Company. RAMpage! is a registered trademark of AST Research, Inc.
- Advantage! is a trademark of AST Research, Inc. IBM and PC/AT are registered
- trademarks of International Business Machines Corporation. PS/2 and Micro
- Channel are trademarks of International Business Machines Corporation. Intel
- is a registered trademark of Intel Corporation. Above is a trademark of
- Intel Corporation. Lotus and 1-2-3 are registered trademarks of Lotus
- Development Company. Microrim and R:BASE are registered trademarks of
- Microrim, Inc.
-
- ████████████████████████████████████████████████████████████████████████████
-
- Preparing for Presentation Manager: Paradox Steps Up to Windows 2.0
-
- ───────────────────────────────────────────────────────────────────────────
- Also see the related article:
- Paradox Under Microsoft(R) OS/2
- ───────────────────────────────────────────────────────────────────────────
-
- Craig Stinson
-
- Ansa Software, the Belmont, Calif., firm recently acquired by Borland
- International, sells only one product, but that product, the relational
- database manager Paradox(R), will appear in four new versions during the
- coming year. A fifth new version will probably be available in early 1989.
-
- The offspring are aimed at different operating environments:
- Microsoft(R) Windows Version 2.0, OS/2 systems Version 1.0, the OS/2
- Presentation Manager, UNIX(R) 5.3 and XENIX(R), and the 80386 running MS-
- DOS(R) 3.x in protected mode with the help of a 386 MS-DOS compatibility
- tool. Paradox 386 was shown at last November's Comdex and will probably be
- available by the time you read this article; it won't require a
- compatibility tool, just a 386 machine. The OS/2 1.0 version should be
- released during the first quarter of 1988, the Windows version in the
- second quarter, the UNIX/XENIX version in the third quarter, and the
- Presentation Manager version in about a year, depending on the date of the
- OS/2 Presentation Manager's arrival.
-
- All Paradox versions, old and new, will have multiuser capability and will
- be data-compatible. With versions for all the major personal computing
- environments (except Macintosh(R)──Ansa is currently evaluating the
- feasibility of a Mac version), Ansa will be in a position to serve the vast
- majority of desktop users. And all users at various nodes of a network will
- be able to access and edit a common database, regardless of the hardware or
- operating systems they're running.
-
-
- Sound Design Pays Off
-
- What type of effort is involved in porting a large, complex MS-DOS
- application such as Paradox to all these different operating environments?
- In a recent interview with MSJ, Richard Schwartz, vice president for
- software development and a cofounder of Ansa, said that the move from one
- character-based system to another has been relatively straightforward,
- thanks largely to the foresight of the original designers of Paradox.
-
- "We designed Paradox from the beginning to be very clean and portable," says
- Schwartz. "We tried not to play dirty tricks getting at the operating system
- or creating hardware dependencies." This is not to say, of course, that the
- original Paradox doesn't write directly to the screen. "We do, of course,
- but we've isolated it. We have one module that will access the screen memory
- map directly. And even the BIOS dependencies are all in one place."
-
- The move from a character environment (MS-DOS 3.x) to a graphics environment
- (Microsoft Windows and the OS/2 Presentation Manager) is a considerably
- more ambitious undertaking. Schwartz talked about the whys and hows of
- this move and about how the graphics-based Paradox will look, feel, and
- perform relative to the original.
-
-
- Why Windows?
-
- The move to Windows now, according to Schwartz, will give Ansa advantages in
- marketing and in implementation when the OS/2 Presentation Manager arrives.
-
- "We think it's important to put out the Windows version, not because Windows
- 2.0 is going to be the primary market-certainly, Presentation Manager
- market will dwarf the Windows market-but because a lot of companies will be
- evaluating the Presentation Manager interface through Windows 2.0. And
- applications that run under Windows early on will have an advantage, given
- the long evaluation cycles that companies go through."
-
- Second, because Windows 2.0 is here now and Presentation Manager is not,
- the effort required to rethink and recode Paradox for Windows is the best
- preparation Ansa can undertake for the eventual port to the Presentation
- Manager-even though the API for Presentation Manager will differ
- significantly from that for Windows.
-
-
- The Good News
-
- How hard was the port to Windows? According to Schwartz, all but the
- underlying database engine was redesigned, borrowing pieces of the
- previous architecture and source code. Nevertheless, he asserts, about 50
- percent of the original source code emigrated to the new world intact.
- That's an off-the-cuff, work-in-progress estimate, but even if it is too
- optimistic, it is still good news for Windows developers: not all programs
- will have to be rewritten from the ground up to run in the new graphics
- environments.
-
- Moreover, in Ansa's case the conversion will probably take about two person-
- years of work. That's not bad, considering that the original Paradox was a
- 14-person-year effort. And the company has done it without hiring a lot of
- specialized expertise. That's also good news, considering the current
- scarcity and high market value of Macintosh and Windows programmers.
-
- "We tried to get some people with product-level graphics experience. We
- used a headhunter for a while, but we were very unsuccessful," says
- Schwartz. "And it turned out that we did very well taking good software
- engineers who have general computer science sophistication and having them
- come up to speed under Windows. We found that general computer science
- background was the most important factor."
-
-
- A Natural Fit
-
- Making Ansa's transition from characters to pixels easier was the fact that
- Paradox, despite its early origins (development for the first version
- began in the summer of 1981), has always been a highly visual, interactive
- program. From an interface design standpoint, therefore, the transfer to
- Windows entailed an expansion of ideas already in place rather than a
- wholesale reconceptualization.
-
- "We've always had a very visual orientation, an object orientation," says
- Schwartz. "And it was just sort of a natural fit."
-
- Data tables in the character-based Paradox, for example, are presented to
- the user in the form of visual constructs that look much like windows,
- except that the borders are made of text-graphic characters and don't go all
- the way around (see Figure 1). Like a frame in Framework or a screenful of a
- spreadsheet, these objects act as portholes onto larger expanses of data,
- which Paradox calls images.
-
- Paradox presents the user with a workspace, much as Windows does, on which
- he can keep several images visible at once. And though it doesn't support a
- mouse, the character-based product lets the user manipulate the sizes and
- locations of images, and the width of columns within them, by positioning
- a highlight in the right hot spot and pressing cursor keys. This ability to
- interact immediately with a data construct, instead of having to issue a
- menu command and describe what one wants, is what Schwartz calls "direct
- operability," an important component of the original Paradox interface.
-
-
- Visual Interaction
-
- "The idea was──as much as possible, anywhere we could──to allow visual
- interaction, rather than descriptive interaction," says Schwartz. "Instead
- of asking the user to explain or describe something, we asked him to show it
- to us. That's very compatible with moving to the Windows environment."
-
- Paradox under Windows will look quite similar at first glance to the
- original version (see Figure 2), except that the Lotus-style menu gives way
- to drop-down menus, the text comes up black-on-white, and the images are
- fully formed document windows, with title bars, scroll bars, control menus,
- and sizing icons. The direct operability will still be there, but there'll
- be more of it. And, of course, there will be mouse support.
-
- One new area of direct operability in Windows Paradox is in the
- specification of sorts. In the character-based version, the user indicated
- which fields he wanted to be sort keys and in which direction he wanted a
- field sorted by typing a field number followed by an "a" for ascending or a
- "d" for descending (see Figure 3).
-
- The Windows version (see Figure 4) simplifies this process and makes it less
- analytical by letting the user double-click directly on the name of each
- sort key field. An arrow indicating sort direction (ascending, by default)
- will appear to the left of the chosen field name; to switch directions, the
- user merely toggles the arrow by double-clicking on it.
-
- The notion of clicking on a value to toggle it or to select from available
- values appears elsewhere in Windows Paradox. In creating or restructuring a
- table, for example, where the user is asked for a data type, he can click
- the mouse to get a menu showing available types. Similarly, in the
- create/restructure subsystem, a double-click on a field name will summon
- a dialog box, allowing the user to inspect current validity-check
- definitions or create new ones.
-
-
- Implementation Advantages
-
- Describing the conversion effort, Schwartz downplays the fearsome Windows
- learning curve, emphasizing instead the implementation advantages
- conferred by the graphics environment. In particular, he cites the greater
- degree of independence among the various objects with which the user
- interacts.
-
- In the Windows environment, every table in the workspace, as well as every
- form, query, and report specification, lives in its own window and has its
- own message handler. That message handler is responsible for knowing only
- about itself, how to repaint itself and how to update itself. In the
- character-based version, more attention had to be paid to the "global
- state," that is, to the ways in which changes in a table would affect
- everything else in the workspace.
-
- "At the implementation level," says Schwartz, "that means that your code
- can have an architecture such that everything is purely local to each
- image. So you can have a local handler that knows how to resize a window or
- how much of it to repaint. If anything in the size of that window is
- affected, Windows takes care of sending messages to the other windows to let
- them know that some other portion needs to be repainted."
-
-
- Debugging Difficulties
-
- According to Schwartz, one fairly serious hindrance to the development of
- Windows Paradox was the lack of debugging tools for use in the Windows
- environment.
-
- "It's a harder problem anyway," Schwartz says, "because there's more going
- behind the scenes in the Windows environment." The movability and
- discardability of Windows object code complicates matters, making bugs
- harder to find. Bugs tend to turn up later in the game as the result of
- stress on the system because a piece of code has been moved in memory.
-
- The current version of CodeView(R) doesn't work in the Windows environment,
- though Microsoft is working on a Windows-specific version. Symdeb is
- provided, but Ansa didn't use it extensively, preferring instead to rely on
- algorithmic-level debugging by embedding printf statements to test values
- of variables at various points in the program.
-
- "In our design environment, we have a monochrome display hooked up as well
- as an EGA. We've hooked things up so that we can put printfs in the code and
- have them scroll out on the monochrome display. You get the effect of
- traditional debugging preserved in the Windows environment."
-
- Making debugging even more difficult was the fact that Ansa was working with
- early prerelease versions of Windows and the Windows development tools (the
- project began last April). Programmers were sometimes hard pressed to tell
- which problems they should attribute to their own code, which to the
- compiler, and which to bugs in the underlying environment.
-
-
- User Advantages
-
- The independence of screen objects provides several benefits for users.
- Most importantly, it means that users can have more things going on at
- once and still keep track of everything. Instead of being limited to the
- number of images that will fit one after the other on screen, the user can
- load up the screen with overlapping windows, thereby achieving whatever
- level of information density he finds most appropriate.
-
- This facilitates such activities as moving from one table to another,
- copying data or structures from one table to another, discovering
- correlations between one set of data and another, and so on. It also means
- that Windows Paradox will appear somewhat less "modal" to users than its
- text-based ancestor. There are places in the current program where a user
- has to drop one context, if only briefly, to work in another. The Windows
- version will minimize the effects of such breaks in continuity.
-
- For example, in the character-based version, if the user wants to modify a
- data-entry form on the fly while entering data, he issues a command to
- summon the forms subsystem. The data and form he was working on disappear
- at that point, becoming visible again only after he's finished changing
- the form spec. In the Windows version, the form subsystem will appear in a
- child window, with the data remaining visible in the parent window.
-
- Other benefits the Windows Paradox user will find include the following:
-
- ■ The Windows version will automatically support any high-resolution
- large-screen displays supported by Windows itself.
-
- ■ The report subsystem will offer enhanced font support and will accept
- bit-mapped images by way of the Windows clipboard. Users can dress up
- Paradox reports with company logos, signatures, clip art, and the
- like. Windows Paradox will not accept bit-mapped images as data
- types, although Schwartz concedes that that's "a natural direction"
- for the product to evolve in.
-
- ■ Windows Paradox can exchange data with other Windows applications by
- way of dynamic data exchange (DDE). At the Microsoft Excel
- announcement, Ansa showed an early version of Paradox running as a
- client of Microsoft Excel, with Paradox started up by means of the
- macro language and transferring data to Microsoft Excel via DDE (see
- Figure 5). The finished Windows version of Paradox will allow a
- reversal of this scenario: you can start up and control other
- applications by means of scripts written in the Paradox application
- language (PAL).
-
-
- Performance Hits
-
- The picture sounds rosy, but Schwartz concedes that the advantages of the
- Windows environment will exact a few performance penalties. The hits, he
- suggests, will come less from Windows' need to represent text in graphics
- mode than from its propensity to swap program modules in and out of memory.
-
- Because the program was still under development at this writing, Ansa was
- not prepared to supply benchmark data. However, Schwartz maintains the
- company is "quite happy with the Paradox performance on 10-MHz 286s and
- above." And, he says, the SMARTDrive program, a Microsoft-supplied disk-
- caching utility tailored to enhance the performance of Windows, speeds
- things up significantly on all machines.
-
-
- Fears Allayed
-
- What messages would Ansa's Schwartz pass along to another Windows
- application developer? The most obvious one is that the redesign of an MS-
- DOS program to run under Windows is not the horrendous task it's commonly
- thought to be, particularly if the program was well designed in the first
- place. A second notion involves the maintenance of individuality under
- conditions that seem to impose similarity.
-
- "Some people say that all products are going to look alike, that all the
- creativity has been taken away from the designer in the Windows
- environment," says Schwartz. "So much has been standardized that
- everything's going to look the same and work the same. That's certainly
- true at some level. Still, there is a very diverse set of ways in which one
- can take advantage of the environment to present a particular application.
-
- "You need to concern yourself first with the core model of how an
- application is to be constructed-with the user-level concepts and how you
- want to present them. And only after that you look at what the Windows tools
- are and how you'll use them. The greatest effect on the user is the
- underlying model, and that is not affected at all by Windows. Windows is
- just a way to present that, to communicate it more effectively to the
- user."
-
-
- ───────────────────────────────────────────────────────────────────────────
- Paradox Under Microsoft(R) OS/2
- ───────────────────────────────────────────────────────────────────────────
-
- Paradox was concieved in the days of very limited resources. Development
- began in the first half of 1981, months before the announcement of the
- IBM(R) PC, when 64Kb was as much memory as anyone could expect to have.
-
- Over the next several years, as larger machines grew commonplace, Paradox
- grew in scope and ambition, so that by the time the the product was
- announced in the Fall of 1885, it required 512Kb to run──practically
- speaking, that meant a 640Kb computer.
-
- However, like a depression era parent, Paradox never forgat what it was like
- to live in lean times. From the beggining, the product incorporated a
- virtual memory manager that swapped data as necessary to the hard disk. One
- of the programs selling points, moreover, was the heuristic approach to the
- processing of relational queries. An important aspect of this process was
- machine reasoning about what data was currently in memory and what was
- swapped out somewhere.
-
- Therefore, the large address space afforded by the 80286 and 80386 chips
- running in protected mode represents a liberation for Paradox, demonstrated
- by the preliminary statistics shown in the accompanying charts.
-
- Judging by the numbers, Paradox running under the OS/2 systems in protected
- mode should take care of complex queries and large scale sorts much more
- quickly than the MS-DOS 3.3 version. The 80386 version will do queries about
- as efficiently as the OS/2 versions and will be even more nimble at sorting.
-
- Perhaps the most surprosing information revealed by these early numbers is
- that Paradox 2.0 running in real mode under OS/2 (in the OS/2 compatibility
- box) outperforms the same product running on the same hardware under MS-DOS
- 3.3. According to Microsoft, most MS-DOS programs will run 5 percent slower
- in the compatibility box. Ansa's Richard Schwartz attributes the improvement
- to the fact that Paradox is doing a lot of random file access and is
- therefore able to profit significantly from OS/'s disk caching.
-
- Performance statistics for the Microsoft Windows version of Paradox were not
- yet available as of this writing.
-
-
- ╔═══╗
- ║ ║100─┐ 93
- ║ ║ │ ▄▄▄ ▒▒ Query ░░ Sort
- ║ ║ │ ░░░█
- ║ ║ 80─┤ 73 ░░░█ 71
- ║ S ║ │ ▄▄▄░░░█ ▄▄▄
- ║ E ║ │ ▒▒▒█░░░█ 59 ░░░█ 60
- ║ C ║ 60─┤ ▒▒▒█░░░█ ▄▄▄░░░█ 46 ▄▄▄ 48
- ║ O ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▄▄▄░░░█ ▄▄▄ 44
- ║ N ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█ ▄▄▄
- ║ D ║ 40─┤ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ║ S ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ║ ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ║ ║ 20─┤ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ║ ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ║ ║ │ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█ ▒▒▒█░░░█
- ╚═══╝ 0─┼─▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀─┐
- Paradox 2.0 Paradox 2.0 Paradox OS/2 Paradox 386
- under DOS 3.3 in the OS/2 in protected under DOS 3.3
- Compatability Box mode
-
- ════════════════════ ELAPSED TIME IN SECONDS ═══════════════════════
-
- Preliminary performance statistics for three new versions of Paradox, as
- measured on a PS/2 Model 80. The query involved a join of a 5000-record
- table and a 10,000-record table; the sort was for the 500-record table. The
- figures for Paradox OS/2 and Paradox 386 may differ when those products are
- shipped.
-
- ───────────────────────────────────────────────────────────────────────────
-
- ╔═P═╗
- ║ E ║ 60─┐ 53%
- ║ R ║ │ ▒▒ Query ▄▄▄▄
- ║ F ║ │ ░░░░█
- ║ O ║ 50─┤ ░░ Sort ░░░░█
- ║ R ║ │ ░░░░█
- ║ M ║ │ 37% ░░░░█
- ║ A ║ 40─┤ ▄▄▄▄ 35% 34% ░░░░█
- ║ N ║ │ ▒▒▒▒█ ▄▄▄▄ ▄▄▄▄░░░░█
- ║ C ║ │ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ E ║ 30─┤ 24% ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ ║ │ ▄▄▄▄ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ I ║ │ 19% ░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ M ║ 20─┤ ▄▄▄▄░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ P ║ │ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ R ║ │ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ O ║ 10─┤ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ V ║ │ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ E ║ │ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█ ▒▒▒▒█░░░░█
- ║ M ║ 0─┴──▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀──┐
- ╚═T═╝ Paradox 2.0 in the Paradox OS/2 in Paradox 386 under
- OS/2 compatability protected mode DOS 3.3
- box
-
- Performance improvements of three new versions of Paradox over Paradox 2.0
- running in MS-DOS 3.3. These figures are preliminary and may not reflect
- actual performance when the products are shipped.
-
- ████████████████████████████████████████████████████████████████████████████
-
- Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager
-
- Micheal Geary
-
- The Microsoft(R) OS/2 Presentation Manager represents the marriage of two
- technologies: the Windows operating environment and the protected-mode OS/2
- operating systems. In any wedding, it's traditional to have something old,
- something new, something borrowed, and something blue, and Presentation
- Manager is no exception.
-
- Something old is Microsoft Windows. In a sense, Presentation Manager is
- really Windows. Although there have been a number of changes from the real-
- mode (MS-DOS(R)) version of Windows, nearly all Windows programming concepts
- carry over directly to the new environment: message-driven programming,
- window functions, resources, and so forth. If you know how to program
- Windows, Presentation Manager will be the icing on the wedding cake.
-
- Something new is the protected-mode hardware and OS/2 operating systems. It
- would not be far from the truth to say this is the environment Windows was
- really meant for. OS/2 offers true preemptive multitasking, unlike the
- "round-robin" multitasking of real-mode Windows. Applications no longer have
- to wait for each other to yield control. Within a single application, you
- can make good use of OS/2's multiple threads of execution, assigning to
- different windows their own threads. The protected-mode hardware greatly
- expands memory addressing capability and eliminates the need for some of the
- software memory management done by real-mode Windows.
-
- Something borrowed is the new Graphics Programming Interface (GPI). Adapted
- from the mainframe graphics GDDM (Graphics Data Display Manager) and GCP
- (Graphics Control Program) standards, GPI replaces Windows' Graphics Device
- Interface (GDI) with a richer, more versatile set of graphics functions.
-
- Something blue, as you've already guessed, is IBM. Presentation Manager
- plays a crucial role in IBM's Systems Application Architecture (SAA) as the
- user interface portion of SAA, at least for machines with graphics
- capabilities. IBM has an ambitious goal for SAA: a common user interface and
- programming interface on a wide range of machines, from PCs to mainframes.
- Eventually you should be able to take a Presentation Manager application and
- compile it to run on any machine. For machines without graphics, a subset of
- Presentation Manager functions will be provided, giving a similar user
- interface, but running in a character mode environment.
-
-
- Presentation Manager Programming
-
- To explore the differences and the similarities between a Presentation
- Manager application and a Windows application, let's look at a sample
- program called SayWhat, which is my entry in the "silliest Windows
- application" contest. I started out with the Hello program that comes with
- the Windows Software Development Kit and doctored it up a bit. Instead of
- just displaying the "hello" message in one place, the text wanders around
- the window randomly, changing its color as it moves. If you make SayWhat
- into an icon, it spells out the message one letter at a time, with the
- letter wandering around the icon. A control panel, a modeless dialog box,
- lets you change the displayed text and adjust how fast it moves and how
- often it selects a new random direction to move in.
-
- ───────────────────────────────────────────────────────────────────────────
- The source code for SayWhat is found at the end of the text
- ───────────────────────────────────────────────────────────────────────────
-
- SayWhat illustrates several programming techniques that are useful for
- Windows and Presentation Manager. It also points out an irritating flaw in
- the way many Windows applications do their screen painting and how to
- improve it. A Windows version and a Presentation Manager version of SayWhat
- are listed side by side for comparison.
-
- When comparing the listings, it is immediately apparent that the overall
- structure of the two programs is identical. The most important aspects of
- Windows apply equally to Presentation Manager: messages, window functions,
- painting, and so on. However, at the detailed coding level we will see many
- differences.
-
-
- Getting Started
-
- At the beginning, both programs have a main function, but in the
- Presentation Manager application it's called main( ) instead of WinMain( )
- as in the Windows application. That's because a Presentation Manager
- application starts out as an ordinary OS/2 application; what makes it
- different is its use of the Presentation Manager API.
-
- The parameters passed to main are different from those that are passed to
- WinMain in the Windows version. The lpszCmdLine parameter is replaced by the
- more conventional, and convenient, argc and argv parameters. There is no
- nCmdShow parameter. If you create your main application window as an
- ordinary visible window by using the WinCreateStdWindow function, this is
- taken care of for you along with the window size. If you require more direct
- control over your window placement, create your main window as invisible and
- then set the size and position yourself. Finally, the hPrevInstance and
- hInstance parameters are missing: hPrevInstance isn't used at all and there
- is nothing in Presentation Manager corresponding to the GetInstanceData
- function. Under OS/2, there is no such thing as an instance in the same
- sense as there is in Windows. There are only OS/2 processes, and each
- process initializes its own data. Subsequent instances of an application
- have exactly the same initialization as the first instance.
-
-
- Message Loop
-
- The main message loop of a Presentation Manager program is nearly identical
- to the main loop of a Windows program. Other than some name changes and the
- use of the hAB parameter, the only difference is that the TranslateMessage
- function is not used. Since this function was always required anyway, it has
- been merged with WinGetMsg. Actually, the keyboard messages under
- Presentation Manager have been substantially simplified. The messages
- WM_KEYDOWN, along with WM_KEYUP, WM_SYSKEYDOWN, and WM_SYSKEYUP have all
- been removed, and the information that they provided is incorporated into
- WM_CHAR. The WM_CHAR message now provides the "raw" keyboard information
- that the WM_KEYDOWN/UP messages used to give, as well as the "translated"
- ASCII character code always provided by WM_CHAR.
-
-
- Initialization
-
- Before the message loop starts up, we have to initialize the application and
- create our main window, which is done in the SayInitApp function. The
- Presentation Manager version begins with two new calls: first, it calls
- WinInitialize which initializes the program for Presentation Manager and
- returns a handle to an anchor block, called hAB in our code. The anchor
- block isn't really used by Presentation Manager, but it's there for
- compatibility with future SAA environments. However, the hAB parameter is
- required on several of the Presentation Manager function calls.
-
- WinInitialize doesn't actually make the application become a Presentation
- Manager application. In fact, kernel applications can call WinInitialize if
- they want to use the local memory management or atom functions provided with
- Presentation Manager. Then it calls WinCreateMsgQueue, which allocates its
- message queue. Unlike Windows, in Presentation Manager you have to do this
- yourself. This gives you more flexibility; you can specify the size of the
- message queue or just take the default by specifying a size of 0, as SayWhat
- does.
-
- WinCreateMsgQueue is that special call that says "yes, this is a
- Presentation Manager application." It establishes this thread of execution
- as a message queue thread, and if you happen to start up a Presentation
- Manager application on a system that has the regular kernel shell running
- instead of the Presentation Manager shell, this is the call that puts you
- into graphics mode. If you have multiple threads, some of the threads may
- have message queues and some may not, but any thread that wants to create
- windows must call WinCreateMsgQueue since threads cannot share a queue.
-
- Both applications then load some string resources. This is nearly identical
- in the two versions, except for the hAB parameter used in the Presentation
- Manager version. Next we pick up display information: the number of colors
- available, the system font size, and the screen size. In the Windows
- version, you create an information context which is like a device context,
- but is used only for gathering information about the device. Then you pick
- up information on the system font with GetTextMetrics, and get the number of
- display planes with a GetDeviceCaps call.
-
- The Presentation Manager version is a little different. Instead of an
- information context or device context, it gets a presentation space with the
- WinGetPS call. Although Presentation Manager also has device contexts, most
- graphics functions use a presentation space instead, which, in a simple
- application like SayWhat, can be used much as a device context would be in
- the Windows version. However, the presentation space is much more powerful
- and versatile; for example, it can record and later play back graphics
- calls, much like a metafile. You could avoid having to process WM_PAINT
- messages this way. (This approach does not enhance performance, but it can
- be a programming convenience for some applications.) The details of
- presentation spaces are not discussed here, since SayWhat doesn't do
- anything fancy with them.
-
- Having obtained the presentation space handle, the Presentation Manager
- version then calls GpiQueryCharBox to get the system font character size and
- GpiQueryColorData to pick up the number of colors. Note that we're using the
- total number of distinct solid colors here, not the number of display planes
- as in the Windows version.
-
-
- Creating a Window
-
- Now you can register your window class and create the main window. It is
- here that you encounter a major difference between Windows and Presentation
- Manager-the architecture of a standard application window.
-
- Under Windows, a standard application window has two main components: the
- client area, which your code normally deals with, and the nonclient area,
- which includes all the controls around the edge of the window; title bar,
- sizing border, menu bar, and so forth. Your window function receives
- messages for the client and nonclient areas, but you usually pass the
- WM_NCxxxxx nonclient messages through to DefWindowProc. Although this works
- for most applications, this architecture has some problems. The behavior of
- all of the nonclient items is hard-coded into Windows. It is possible to
- override some of this behavior by intercepting the WM_NCxxxxx messages, but
- this is fairly cumbersome and very limited. There are also some strange
- inconsistencies, like the fact that standard scroll bars are part of the
- special nonclient area, but any non-standard scroll bars are created as
- child windows and are treated differently.
-
- When I was coding the window editor in one of my applications, this
- client/nonclient architecture was a serious obstacle. I needed complete
- control over all aspects of a window's behavior, because my application lets
- the user build and edit window characteristics on the fly. In design mode, I
- didn't want the nonclient items to behave normally, since they were objects
- being edited at that point. I finally got things to work reasonably well,
- but not quite the way I wanted. For example, if the user wished to remove
- the Maximize icon from a window, I would have to destroy and recreate the
- entire window.
-
- Under Presentation Manager, this client/nonclient architecture has been
- eliminated. The functionality is still there, but child windows and some new
- predefined window classes implement it. All of the items that were part of
- the nonclient area are now child windows, and you can do special things with
- them simply by subclassing them as you would any other window. There is a
- new window class, the frame window, which acts as a container for all these
- child windows and has the smarts to position them appropriately according to
- the window size. What used to be the client area under Windows is now itself
- a child window of the frame window.
-
- Now, for many applications, all of this won't make much difference. You can
- use the WinCreateStdWindow function to set up a standard frame window with
- the desired controls around the border, and things will work pretty much as
- before. The only significant difference is that some operations that were
- performed with Windows function calls are now done by sending messages to
- the various controls.
-
- However, in an application where the window frame controls are treated
- specially, this new architecture makes life much easier. Subclassing a child
- window is a lot more convenient than trying to fool all of the WM_NCxxxxx
- messages; it's a simpler, more consistent architecture. It is even
- reasonable to add more special controls to the window frame, placed anywhere
- you want.
-
- There's another, more subtle area where the window architecture has been
- cleaned up. Under Windows, every window logically has a parent window and an
- owner window. The parent determines how screen space is used-a child window
- is contained within its parent and clipped at the edges of the parent. The
- owner determines two things; control windows (edit controls, for example)
- send notification messages to their owner, and pop-up windows disappear when
- their owner is minimized. These are two different steps, although Windows
- combines them into a single "parent" window handle and interprets this
- handle according to the window style bits. For a WS_CHILD window, the
- "parent" is actually both the parent and the owner. For a WS_POPUP, the
- actual parent is NULL, and the "parent" is really the owner.
-
- Presentation Manager separates the parent and owner window handles, letting
- you specify them individually. This removes the need for the WS_CHILD,
- WS_POPUP, and WS_OVERLAPPED window styles; instead, a window is just a
- window. Furthermore, every window has a parent, because there is one new
- window, called the desktop window, which occupies the entire screen. Every
- top level window is actually a child of the desktop window.
-
- With all this in mind, let's look at the WinRegisterClass and
- WinCreateStdWindow calls in SayWhat. You'll see that there isn't much to the
- WinRegisterClass call. The WNDCLASS structure from the Windows version is
- gone, because most of what it specified has migrated to the frame window
- class and also to WinCreateStdWindow. One interesting new option in
- WinRegisterClass is the CS_SYNCPAINT style bit. If this bit is set, then
- WM_PAINT messages for windows of this class are not deferred in the
- customary fashion. Instead, WM_PAINT is sent immediately whenever the window
- becomes invalidated. This is how the window frame controls get repainted
- immediately; they have the CS_SYNCPAINT style. In the Windows version, this
- is handled specially by WM_PAINT, but under Presentation Manager, this
- feature is available to any window. Since SayWhat's window painting
- procedure is very simple and quite fast, CS_SYNCPAINT makes for a nice clean
- display. A window class that takes longer to paint should omit this style
- bit, and its windows will receive deferred WM_PAINT messages just as they do
- under Windows.
-
- The WinCreateStdWindow call creates SayWhat's frame window, along with its
- client window and all the frame controls. The frame window handle and the
- client window handle are returned by this call, the latter through the
- pointer in the last parameter. The window style bits look pretty familiar,
- but many of them have FS_ names instead of WS_ names because they are really
- just indicators to the frame window telling it which child windows to
- create. Also, there's no size or position information in this call. The
- trick is that if you give the window the WS_VISIBLE style, it will
- automatically be assigned the appropriate default size and position.
- Alternatively, you can create it as invisible and place it where you want
- (and then make it visible) explicitly with the call WinSetWindowPos.
-
- WinCreateStdWindow is just a convenience for creating standard frame
- windows. There is also a generic WinCreateWindow that can create any kind of
- window, giving you total control over how the window is set up. You would
- use this function to create additional child windows or any other kind of
- special window.
-
-
- Window Functions
-
- Now that our window has been created, it's time to look at the window
- function, SayWhatWndProc. You'll notice that the window functions in the two
- versions are very similar. As I mentioned before, all of the keyboard input
- is done under a WM_CHAR message, not under WM_KEYDOWN as in the Windows
- version, but otherwise it's essentially the same. Mouse input is nearly
- identical also, except that the message names have been changed to avoid the
- inconsistency of the right button sending a message called WM_LBUTTONUP/DOWN
- when the buttons have been swapped-the main button is instead WM_BUTTON1.
-
- The other messages processed by SayWhatWndProc are also extremely similar;
- WM_CREATE, WM_DESTROY, WM_SIZE, WM_PAINT, WM_TIMER, and WM_COMMAND. One very
- apparent difference is that there is no corresponding wParem = = SIZEICONIC,
- but there is a new WM_MINMAX message that provides the same information.
- WM_MINMAX is thus intercepted and processed to find out when the window is
- being minimized or restored to the normal state.
-
- There are some differences in the message parameters for all messages,
- though. Instead of a 16-bit parameter and a 32-bit parameter, wParam and
- lParam, there are two 32-bit parameters; lParam1 and lParam2. This allows a
- little more information to be passed along with messages, but the real
- reason for this change was for those messages that pass an additional window
- handle in wParam, such as WM_VSCROLLCLIPBOARD. Under Presentation Manager,
- window handles and most other handles are 32 bits, not 16 bits, so the
- wParam had to be expanded to permit this. The hWnd parameter to the window
- function is also a 32-bit parameter. Watch out for this one if you have any
- code that assumes handles are 16 bits.
-
-
- Window Painting
-
- The SayWhatPaint function handles window painting in both versions. It is
- rather similar in the two versions, although the Presentation Manager
- version has no PAINTSTRUCT. Instead, the WinBeginPaint function returns a
- presentation space handle and gives you the update rectangle as a separate
- parameter. In addition, it makes use of GpiSetColor instead of using
- SetTextColor, and uses GpiCharStringAt instead of TextOut. The same work is
- being performed in both cases.
-
- There's a little twist in SayWhat's painting logic in both versions. When
- you run SayWhat, you will notice that the text wanders around the window
- cleanly. There is no flicker, even though the text is repainted from scratch
- each time. Many Windows applications have a little bit of flicker when they
- paint into their window, which is caused by the usual practice of erasing
- the background completely and then painting the nonblank portions. That's
- fine when you paint a window for the first time, but if you want to repaint
- a portion of the window, there is a split second where the background is
- blanked out.
-
- You've probably seen this effect running Notepad or Write. If you type
- quickly in either program, the line you are on will flicker a little instead
- of painting cleanly. In SayWhat, you can see the same problem by selecting
- the Flicker option on the control dialog box. It's even worse here because
- the window gets repainted so frequently.
-
- How does SayWhat avoid this flicker in its normal mode? Simple: it doesn't
- erase the background underneath the text it's about to paint. It does erase
- any other portions of the background if needed, but where the text itself
- will go, it just writes the text without erasing first. This works because
- text is normally written opaquely; it completely covers whatever is
- underneath it.
-
- There are two different painting routines to illustrate this in
- SayWhatPaint. If the bCleanPaint variable is FALSE, it uses the common,
- flickery painting method, that is, it first erases the entire background and
- then draws the text. If bCleanPaint is TRUE, it draws the text and erases
- only the remaining portions of the background.
-
- It may seem silly to get worked up about this, but fixing it dramatically
- improves the appearance of an application. I know this from my own
- experience: the outline editor in my own application originally had a lot of
- irritating flicker, which went away when I converted it to use the clean
- painting method. And, even though it took a little extra code in the paint
- function, it actually reduced the overall amount of code and complexity. The
- flicker had been so annoying that I was ready to put in all kinds of special
- cases to make sure I never invalidated more of the window than was
- necessary, but with the clean painting method, it no longer matters.
- Painting is totally invisible to the user now, so if I invalidate a little
- too much, no one is the wiser.
-
-
- Dialog Boxes
-
- SayWhat has two dialog boxes: a modal About box and a modeless control
- panel, which are both created under the WM_COMMAND case in SayWhatWndProc.
- The code in both versions is similar except that MakeProcInstance is not
- necessary in the protected-mode environment. OS/2 and the memory management
- hardware set up the proper data segment for "call-back" functions such as
- dialog functions. This is a blessing for anyone who has struggled with
- mysterious system crashes caused by leaving out a MakeProcInstance under
- Windows. However, you still need to remember to EXPORT your call back
- functions and window functions, as was the case before.
-
- There is one important difference in the implementation of the dialog box
- functions themselves. Under Windows, a dialog box function is not coded like
- a normal window function. In fact, the actual window function for all dialog
- boxes is a private function inside Windows called DlgWndProc, which calls
- your dialog function before doing its own message switch statement and if it
- returns nonzero, it skips its own message processing. One problem with this
- approach is that it's a little confusing to have dialog functions work
- differently from normal window functions. Another problem is that it is
- impossible for a dialog function to return a value back to a SendMessage
- call. The return value you give isn't returned back through SendMessage;
- DlgWndProc simply returns zero. (Odd as it may seem, DlgWndProc treats the
- WM_CTLCOLOR message as a special case and does return the value you
- provide.)
-
- Under Presentation Manager, a dialog function operates like any other window
- function. Its return value is the real return value, and there is a default
- message function, WinDefDlgProc, that you call for messages that you don't
- process yourself. SayAboutDlgProc illustrates this difference and, like most
- About boxes, doesn't do much else except close itself when you hit the OK
- button.
-
- The other dialog box, SayWhat's control panel, is a little more interesting.
- This dialog box has no owner window (parent window under Windows). Usually,
- you specify an owner window when you create a dialog box, which causes three
- things to happen: the box is automatically positioned relative to the
- parent, it is always on top of the parent, and it disappears if the parent
- is made into an icon. However, in SayWhat, I didn't want the latter two
- actions; I wanted the box to remain visible if SayWhat's window itself is
- made into an icon so that you could fiddle with the parameters while the
- icon is displayed. I also wanted to be able to put SayWhat's main window on
- top of the dialog box.
-
- Creating the dialog box with a NULL owner takes care of this; however, I
- still wanted the box to be positioned relative to the main window instead of
- being relative to the screen. So, the WM_INITDIALOG code does this
- positioning explicitly, using ClientToScreen calls in the Windows version
- and a WinMapWindowPoints call in the Presentation Manager version.
- WinMapWindowPoints is a handy function, which replaces ClientToScreen and
- ScreenToClient, since you can specify a source window, a destination window,
- or both. It will also map as many points as you want in one call. In this
- program it maps two points, that is, one rectangle.
-
- After the mapping, you have to make sure that you haven't moved the dialog
- box partially offscreen, so you check it against the screen boundaries and
- adjust it back if necessary. Then, you call SetWindowPos or WinSetWindowPos
- to set the new position. This function works a little differently under
- Presentation Manager. If you have used SetWindowPos in Windows 2.0, you know
- it can do a number of things at once, such as move the window, resize it, or
- change the window ordering. The default is for it to do all of these, and in
- the last parameter you tell it what you do not want it to do.
- WinSetWindowPos does the same, except the last parameter tells it what you
- do want it to do-just the opposite.
-
- Note that the y parameter of WinSetWindowPos takes the window bottom, not
- the window top. This shows one subtle difference between Windows and
- Presentation Manager: vertical coordinates run from bottom to top, not top
- to bottom, that is, position (0,0) is the lower-left corner of a window or
- the screen, not the upper-left corner. To be consistent with this, a
- rectangle structure is now stored in the order (left, bottom, right, top)
- instead of (left, top, right, bottom). If you're familiar with the various
- GDI mapping modes available in Windows, you have probably noticed that they
- all run from bottom to top except for MM_TEXT, which runs from top to
- bottom. This change in Presentation Manager removes that inconsistency──all
- coordinates normally run the same direction now──but it obviously requires
- some conversion effort. You can instruct Presentation Manager to use the
- top-to-bottom coordinates within a window, so that could help in converting
- existing applications.
-
- After positioning the dialog box, the WM_INITDIALOG code initializes its
- control windows. One item of interest here is the scroll bar initialization
- in SayInitBar. The Windows version calls two scroll bar functions,
- SetScrollRange and SetScrollPos, to initialize the scroll bar. Presentation
- Manager deals with these two at the same time, with a single
- SBM_SETSCROLLBAR message that sets the position and range. Also, you will
- find throughout Presentation Manager that this is performed with a message
- instead of a special function; many of the special-purpose functions for
- control windows have been removed in favor of messages. If you prefer the
- approach of calling functions, you can always write your own equivalent
- functions that send the appropriate messages.
-
- The other interesting messages in SayWhatDlgProc are WM_COMMAND and
- WM_HSCROLL. The latter message gets sent for activity in either of the
- dialog box's scroll bars, and it calls SayDoBarMsg to set the new scroll bar
- position and to set the corresponding edit control value. However, in
- Presentation Manager this message passes you the child window ID of the
- scroll bar control instead of the window handle. This is convenient, since
- you need the window ID to tell which scroll bar you're dealing with. The
- WM_COMMAND message is sent when the user clicks any of the pushbuttons or
- radio buttons in the dialog box. (It also gets sent when you type in one of
- the edit controls, but SayWhat ignores it.) The WM_COMMAND processing is
- nearly identical in the two versions, except that the IsDlgButtonChecked
- function call is replaced with a BM_QUERYCHECK message in Presentation
- Manager.
-
-
- Conclusion
-
- As you can see, converting a Windows application to Presentation Manager
- isn't trivial, but it is pretty straightforward. The similarities between
- the two systems far outweigh the differences. If you're fluent in Windows
- programming, you've already done the hard part, and you know how different a
- Windows application is from a conventional MS-DOS or OS/2 program. If you
- haven't gotten started with Windows yet, well.... What's that I hear in the
- distance? Yes, it's wedding bells. Don't be late!
-
- ───────────────────────────────────────────────────────────────────────────
- Source code in this article is referenced W for Windows programs and PM for
- Presentation Manager programs.
- ───────────────────────────────────────────────────────────────────────────
-
-
- Figure 1W: SAYWHAT is the MAKE File for SAYWHAT
-
- # SAYWHAT
- # MAKE file for SAYWHAT (Windows version)
-
- sw.obj: sw.c sw.h
- cl -c -AS -DLINT_ARGS -Gcsw -Oas -W3 -Zdp sw.c
-
- sw.res: sw.rc sw.h
- rc -r sw.rc
-
- saywhat.exe: sw.obj sw.res sw.def
- link4 sw, saywhat/align:16, saywhat/map/line, slibw, sw.def
- mapsym saywhat
- rc sw.res saywhat.exe
-
-
- Figure 1PM: SAYWHAT is the MAKE File for SAYWHAT
-
- # SAYWHATP
- # MAKE file for SAYWHAT (Presentation Manager version)
-
- swp.obj: swp.c swp.h
- cl -c -AS -DLINT_ARGS -G2csw -Oat -W3 -Zp swp.c
-
- swp.res: swp.rc swp.h
- rc -r swp.rc
-
- saywhatp.exe: swp.obj swp.res swp.def
- link @swp.lnk
- mapsym saywhat
- rc swp.res saywhat.exe
-
-
- Figure 2W:
-
- Note that there is no Windows equivalent for SAYWHATP.LNK, the LINK file
- for the PM version of SAYWHAT.
-
-
- Figure 2PM: SWP.LNK is the Link File for SAYWHAT
-
- SWP.LNK
-
- swp
- saywhatp/align:16
- saywhatp/map
- wincalls doscalls mlibc286/NOD
- swp.def
-
-
- Figure 3W: SW.DEF is the Module Definition File for SAYWHAT
-
- ; SW.DEF
- ; SW.DEF - Module definition file for SAYWHAT (Windows version)
-
-
- NAME SayWhat
-
-
- DESCRIPTION 'Say What!'
-
-
- STUB 'WINSTUB.EXE'
-
- CODE MOVEABLE
- DATA MOVEABLE MULTIPLE
-
- HEAPSIZE 128
- STACKSIZE 4096
-
- EXPORTS
- SayAboutDlgProc @1
- SayWhatDlgProc @2
- SayWhatWndProc @3
-
-
- Figure 3PM: SWP.DEF is the Module Definition File for SAYWHAT
-
- ; SWP.DEF
- ; SWP.DEF - Module definition file for SAYWHAT (PM version)
-
- NAME SayWhat
-
- DESCRIPTION 'Say What!'
-
- STUB 'OS2STUB.EXE'
-
- CODE MOVEABLE
- DATA MOVEABLE MULTIPLE
-
- HEAPSIZE 128
- STACKSIZE 8192
-
- EXPORTS
- SayAboutDlgProc @1
- SayWhatDlgProc @2
- SayWhatWndProc @3
-
-
- Figure 4W: SW.RC is the Resource File for SAYWHAT
-
- SW.RC
-
- #include <style.h>
- #include "sw.h"
-
- STRINGTABLE
- BEGIN
- STR_NAME, "SayWhat!"
- STR_TITLE, "Say What!"
- STR_WHAT, "Hello Windows!"
- END
-
- SayWhat! MENU
- BEGIN
-
- POPUP "&Say"
- BEGIN
- MENUITEM "&What..." , CMD_WHAT
- MENUITEM SEPARATOR
- MENUITEM "E&xit" , CMD_EXIT
- MENUITEM SEPARATOR
- MENUITEM "A&bout SayWhat!..." , CMD_ABOUT
- END
-
- END
-
- DLG_ABOUT DIALOG 19, 17, 130, 83
- STYLE WS_DLGFRAME | WS_POPUP
- BEGIN
- CTEXT "Microsoft Windows", -1, 0, 8, 127, 8
- CTEXT "Say What!", -1, 0, 18, 127, 8
- CTEXT "Version 1.00", -1, 0, 30, 127, 8
- CTEXT "By Michael Geary", -1, 0, 44, 129, 8
- DEFPUSHBUTTON "Ok", IDOK, 48, 62, 32, 14
- END
-
- DLG_WHAT DIALOG 49, 41, 177, 103
- CAPTION "Say What!"
- STYLE WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_POPUP
- BEGIN
- CONTROL "Say &What:" -1, "Static", SS_LEFT | WS_GROUP,
- CONTROL "" ITEM_WHAT, "Edit", ES_LEFT | ES_AUTOHSCROLL
- CONTROL "&Time Delay:" -1, "Static", SS_LEFT,
- CONTROL "" ITEM_INTBAR, "ScrollBar", SBS_HORZ | WS_TABSTOP,
- CONTROL "" ITEM_INTERVAL, "Edit", ES_LEFT | WS_BORDER | WS
- CONTROL "&Stability:" -1, "Static", SS_LEFT,
- CONTROL "" ITEM_DISTBAR, "ScrollBar", SBS_HORZ | WS_TABSTOP,
- CONTROL "" ITEM_DISTANCE, "Edit", ES_LEFT | WS_BORDER | WS_
- CONTROL "Painting:" -1, "Static", SS_LEFT,
- CONTROL "&Clean" ITEM_CLEAN, "Button", BS_AUTORADIOBUTTON | WS_G
- CONTROL "&Flicker" ITEM_FLICKER, "Button", BS_AUTORADIOBUTTON,
- CONTROL "Enter" IDOK, "Button", BS_DEFPUSHBUTTON | WS_GRO
- CONTROL "Esc=Close" IDCANCEL, "Button", BS_PUSHBUTTON | WS_TABSTO
- END
-
-
- Figure 4PM: SWP.RC is the Resource File for SAYWHAT
-
- SWP.RC
-
- #include <style.h>
- #include "sw.h"
-
- STRINGTABLE
- {
- STR_NAME, "SayWhat!"
- STR_TITLE, "Say What!"
- STR_WHAT, "Hello Windows!"
- }
-
- MENU SayWhat!
- {
- SUBMENU "~Say", -1
- {
- MENUITEM "~What...", CMD_WHAT
- MENUITEM SEPARATOR
- MENUITEM "E~xit", CMD_EXIT
- MENUITEM SEPARATOR
- MENUITEM "A~bout SayWhat!...", CMD_ABOUT
- }
- }
-
- DLGTEMPLATE DLG_ABOUT
- {
- DIALOG "", DLG_ABOUT, 19, 17, 130, 83, FS_DLGBORDER |
- WS_SAVEBITS | WS_VISIBLE
- {
- CTEXT "Microsoft Windows", -1, 0, 8, 127, 8
- CTEXT "Say What!", -1, 0, 18, 127, 8
- CTEXT "Version 1.00", -1, 0, 30, 127, 8
- CTEXT "By Michael Geary", -1, 0, 44, 129, 8
- DEFPUSHBUTTON "Ok", IDOK, 48, 62, 32, 14
- }
- }
-
- DLGTEMPLATE DLG_WHAT
- {
- DIALOG "Say What!", DLG_WHAT, 49, 41, 177, 103,
- FS_TITLEBAR | FS_SYSMENU | FS_BORDER | WS_VISIBLE
- {
- CONTROL "Say ~What:" -1, 8, 10, 40, 8, WC_STATIC, SS_LE
- CONTROL "" ITEM_WHAT, 56, 8, 112, 12, WC_EDIT, ES_LE
- CONTROL "~Time Delay:" -1, 8, 28, 53, 9, WC_STATIC, SS_LE
- CONTROL "" ITEM_INTBAR, 56, 28, 88, 8, WC_SCROLLBAR, SBS_H
- CONTROL "" ITEM_INTERVAL, 152, 26, 16, 12, WC_EDIT, ES_LE
- CONTROL "~Stability:" -1, 8, 46, 56, 9, WC_STATIC, SS_LE
- CONTROL "" ITEM_DISTBAR, 56, 46, 88, 8, WC_SCROLLBAR, SBS_H
- CONTROL "" ITEM_DISTANCE, 152, 44, 16, 12, WC_EDIT, ES_LE
- CONTROL "Painting:" -1, 8, 64, 40, 8, WC_STATIC, SS_LE
- CONTROL "~Clean" ITEM_CLEAN, 56, 62, 36, 12, WC_BUTTON, BS_AU
- CONTROL "~Flicker" ITEM_FLICKER, 96, 62, 42, 12, WC_BUTTON, BS_AU
- CONTROL "Enter" IDOK, 24, 82, 48, 14, WC_BUTTON, BS_DE
- CONTROL "Esc=Close" IDCANCEL, 104, 82, 48, 14, WC_BUTTON, BS_PU
- }
- }
-
-
- Figure 5W: SW.H is the C Header file for SAYWHAT
-
- /* SW.H */
- /* SW.H - C header file for SAYWHAT (Windows version) */
-
- /* String table constants */
-
- #define STR_NAME 101
- #define STR_TITLE 102
- #define STR_WHAT 103
-
- /* Menu command IDs */
-
- #define CMD_ABOUT 201
- #define CMD_EXIT 202
- #define CMD_WHAT 203
-
- /* Dialog box resource IDs */
-
- #define DLG_ABOUT 301
- #define DLG_WHAT 302
-
- /* 'What...' dialog box item IDs */
-
- #define ITEM_WHAT 401
- #define ITEM_INTBAR 402
- #define ITEM_INTERVAL 403
- #define ITEM_DISTBAR 404
- #define ITEM_DISTANCE 405
- #define ITEM_CLEAN 406
- #define ITEM_FLICKER 407
-
- /* Timer IDs */
-
- #define TIMER_MOVE 501
- #define TIMER_CHAR 502
-
-
- Figure 5PM: SWP.H is the C Header File for SAYWHAT
-
- /* SWP.H */
- /* SWP.H - C header file for SAYWHAT (PM version) */
-
- /* String table constants */
-
- #define STR_NAME 101
- #define STR_TITLE 102
- #define STR_WHAT 103
-
- /* Menu command IDs */
-
- #define CMD_ABOUT 201
- #define CMD_EXIT 202
- #define CMD_WHAT 203
-
- /* Dialog box resource IDs */
-
- #define DLG_ABOUT 301
- #define DLG_WHAT 302
-
- /* 'What...' dialog box item IDs */
-
- #define ITEM_WHAT 401
- #define ITEM_INTBAR 402
- #define ITEM_INTERVAL 403
-
- #define ITEM_DISTBAR 404
- #define ITEM_DISTANCE 405
- #define ITEM_CLEAN 406
- #define ITEM_FLICKER 407
-
- /* Timer IDs */
-
- #define TIMER_MOVE 501
- #define TIMER_CHAR 502
-
- /* Menu resource IDs */
-
- #define MENU_WHAT 601
-
-
- Figure 6W: SW.C is the C Source Code Listing for SAYWHAT
-
- /* SW.C
- * SW.C - C code for SayWhat - Windows version
- */
-
- /* Note - this code *must* be compiled with the -Gc switch */
-
- #ifndef LINT_ARGS
- #define LINT_ARGS /* turn on argument checking for C runtime */
- #endif
-
- #include <windows.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include "sw.h"
-
- #define MIN_INTERVAL 1 /* limits for nInterval */
- #define MAX_INTERVAL 999
-
- #define MIN_DISTANCE 1 /* limits for nDistance */
- #define MAX_DISTANCE 99
-
- /* Static variables */
-
- HANDLE hInstance; /* application instance handle */
-
- HWND hWndWhat; /* main window handle */
- HWND hWndPanel; /* contral panel dialog handle */
-
- FARPROC lpfnDlgProc; /* ProcInstance for dialog */
-
- char szAppName[10]; /* window class name */
- char szTitle[15]; /* main window title */
-
- char szText[40]; /* current "what" text */
- NPSTR pText = szText; /* ptr into szText for icon mode */
- char cBlank = ' '; /* a blank we can point to */
-
- RECT rcText; /* current text rectangle */
- POINT ptAdvance; /* increments for SayAdvanceText */
- POINT ptCharSize; /* X and Y size of a character */
- POINT ptScreenSize; /* X and Y size of the screen */
-
- int nDisplayPlanes; /* number of display planes */
- DWORD rgbTextColor; /* current text color */
- HBRUSH hbrBkgd; /* brush for erasing background */
-
- int nInterval = 40; /* current "Interval" setting */
- int nDistance = 30; /* current "Distance" setting */
- int nDistLeft = 0; /* change direction when hits 0 */
- BOOL bCleanPaint = TRUE; /* clean or flickery painting? */
- BOOL bMouseDown = FALSE; /* is mouse down? */
- BOOL bIconic = FALSE; /* is main window iconic? */
-
- /* Full prototypes for our functions to get type checking */
-
- BOOL FAR SayAboutDlgProc( HWND, unsigned, WORD, LONG );
- void SayAdvanceTextChar( HWND );
- void SayAdvanceTextPos( HWND );
- void SayChangeColor( HWND );
- void SayDoBarMsg( HWND, HWND, WORD, int );
- void SayFillRect( HDC, int, int, int, int );
- void SayInitBar( HWND, int, int, int, int );
- BOOL SayInitApp( HANDLE, int );
- void SayInvalidateText( HWND );
- void SayLimitTextPos( HWND );
- void SayMoveText( HWND, POINT );
- void SaySetBar( HWND, int *, int );
- void SayExitApp( int );
- BOOL FAR SayWhatDlgProc( HWND, unsigned, WORD, LONG );
- void SayWhatPaint( HWND );
- LONG FAR SayWhatWndProc( HWND, unsigned, WORD, LONG );
- void WinMain( HANDLE, HANDLE, LPSTR, int );
-
- /* Dialog function for the "About" box */
-
- BOOL FAR SayAboutDlgProc( hWndDlg, wMsg, wParam, lParam )
- HWND hWndDlg;
- unsigned wMsg;
- WORD wParam;
- LONG lParam;
- {
- switch( wMsg )
- {
- case WM_COMMAND:
- switch( wParam )
- {
- case IDOK:
- EndDialog( hWndDlg, TRUE );
- return TRUE;
- }
- }
- return FALSE;
- }
-
- /* Advances to next display character in iconic mode.
- * Forces in a blank when it reaches the end of string.
- */
-
- void SayAdvanceTextChar( hWnd )
- HWND hWnd;
- {
- if( ! bIconic )
- return;
-
- if( pText = = &cBlank )
- pText = szText;
- else if( ! *(++pText) )
- pText = &cBlank;
-
- SayChangeColor( hWnd );
- SayInvalidateText( hWnd );
- }
-
- /* Advances text position according to ptAdvance. Decrements
- * nDistLeft first, and when it reaches zero, sets a new
- * randomized ptAdvance and nDistLeft, also changes color.
- * Does nothing if mouse is down, so text will track mouse.
- */
-
- void SayAdvanceTextPos( hWnd )
- HWND hWnd;
- {
- int i;
-
- if( bMouseDown )
- return;
-
- SayInvalidateText( hWnd );
-
- if( nDistLeft- < 0 ) {
- nDistLeft = rand() % nDistance + 1;
- do {
- i = rand();
- ptAdvance.x = (
- i < SHRT_MAX/3 ? -1
- : i < SHRT_MAX/3*2 ? 0
- : 1
- );
- i = rand();
- ptAdvance.y = (
- i < SHRT_MAX/3 ? -1
- : i < SHRT_MAX/3*2 ? 0
- : 1
- );
- } while( ptAdvance.x = = 0 && ptAdvance.y = = 0 );
-
- if( ! bIconic )
- SayChangeColor( hWnd );
- } else {
- rcText.left += ptAdvance.x;
- rcText.right += ptAdvance.x;
- rcText.top += ptAdvance.y;
- rcText.bottom += ptAdvance.y;
- }
-
- SayInvalidateText( hWnd );
- }
-
- /* Changes color to a random selection, if color is available.
- * Forces a color change - if the random selection is the same
- * as the old one, it tries again.
- */
-
- void SayChangeColor( hWnd )
- HWND hWnd;
- {
- HDC hDC;
- DWORD rgbNew;
- DWORD rgbWindow;
-
- if( nDisplayPlanes <= 1 ) {
- rgbTextColor = GetSysColor(COLOR_WINDOWTEXT);
- } else {
- rgbWindow = GetSysColor(COLOR_WINDOW);
- hDC = GetDC( hWnd );
- do {
- rgbNew = GetNearestColor(
- hDC,
- MAKELONG( rand(), rand() ) & 0x00FFFFFFL
- );
- } while( rgbNew = = rgbWindow || rgbNew = = rgbTextColor );
- rgbTextColor = rgbNew;
- ReleaseDC( hWnd, hDC );
- }
- }
-
- /* Handles scroll bar messages from the control dialog box.
- * Adjusts scroll bar position, taking its limits into account,
- * copies the scroll bar value into the adjacent edit control,
- * then sets the nDistance or nInterval variable appropriately.
- */
-
- void SayDoBarMsg( hWndDlg, hWndBar, wCode, nThumb )
- HWND hWndDlg;
- HWND hWndBar;
- WORD wCode;
- int nThumb;
- {
- int nPos;
- int nOldPos;
- int nMin;
- int nMax;
- int idBar;
-
- idBar = GetWindowWord( hWndBar, GWW_ID );
-
- nOldPos = nPos = GetScrollPos( hWndBar, SB_CTL );
- GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );
-
- switch( wCode )
- {
- case SB_LINEUP: -nPos; break;
-
- case SB_LINEDOWN: ++nPos; break;
-
- case SB_PAGEUP: nPos -= 10; break;
-
- case SB_PAGEDOWN: nPos += 10; break;
-
- case SB_THUMBPOSITION:
- case SB_THUMBTRACK: nPos = nThumb; break;
-
- case SB_TOP: nPos = nMin; break;
-
- case SB_BOTTOM: nPos = nMax; break;
- }
-
- if( nPos < nMin )
- nPos = nMin;
-
- if( nPos > nMax )
- nPos = nMax;
-
- if( nPos = = nOldPos )
- return;
-
- SetScrollPos( hWndBar, SB_CTL, nPos, TRUE );
-
- SetDlgItemInt( hWndDlg, idBar+1, nPos, FALSE );
-
- switch( idBar )
- {
- case ITEM_DISTBAR:
- nDistance = nPos;
- break;
-
- case ITEM_INTBAR:
- KillTimer( hWndWhat, TIMER_MOVE );
- nInterval = nPos;
- SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
- InvalidateRect( hWndWhat, NULL, FALSE );
- break;
- }
- }
-
- /* Terminates the application, freeing up allocated resources.
- * Note that this function does NOT return to the caller, but
- * exits the program.
- */
-
- void SayExitApp( nRet )
- int nRet;
- {
- if( GetModuleUsage(hInstance) = = 1 ) {
- DeleteObject( hbrBkgd );
- }
-
- exit( nRet );
- }
-
- /* Fills a specified rectangle with the background color.
- * Checks that the rectangle is non-empty first.
- */
-
- void SayFillRect( hDC, nLeft, nTop, nRight, nBottom )
- HDC hDC;
- int nLeft;
- int nTop;
- int nRight;
- int nBottom;
-
- {
- RECT rcFill;
-
- if( nLeft >= nRight || nTop >= nBottom )
- return;
-
- SetRect( &rcFill, nLeft, nTop, nRight, nBottom );
-
- FillRect( hDC, &rcFill, hbrBkgd );
- }
-
- /* Initializes the application. */
-
- BOOL SayInitApp( hPrevInstance, nCmdShow )
-
- HANDLE hPrevInstance;
- int nCmdShow;
- {
- WNDCLASS Class;
- HDC hDC;
- TEXTMETRIC Metrics;
-
- LoadString(
- hInstance, STR_NAME, szAppName, sizeof(szAppName)
- );
- LoadString(
- hInstance, STR_TITLE, szTitle, sizeof(szTitle)
- );
- LoadString(
- hInstance, STR_WHAT, szText, sizeof(szText)
- );
-
- hDC = CreateIC( "DISPLAY", NULL, NULL, NULL );
- GetTextMetrics( hDC, &Metrics );
- nDisplayPlanes = GetDeviceCaps( hDC, PLANES );
- DeleteDC( hDC );
-
- ptCharSize.x = Metrics.tmMaxCharWidth;
- ptCharSize.y = Metrics.tmHeight;
-
- ptScreenSize.x = GetSystemMetrics(SM_CXSCREEN);
- ptScreenSize.y = GetSystemMetrics(SM_CYSCREEN);
-
- if( ! hPrevInstance ) {
-
- hbrBkgd = CreateSolidBrush( GetSysColor(COLOR_WINDOW) );
-
- Class.style = 0; /* CS_HREDRAW | CS_VREDRAW; */
- Class.lpfnWndProc = SayWhatWndProc;
- Class.cbClsExtra = 0;
- Class.cbWndExtra = 0;
- Class.hInstance = hInstance;
- Class.hIcon = NULL;
- Class.hCursor = LoadCursor( NULL, IDC_ARROW );
- Class.hbrBackground = COLOR_WINDOW + 1;
- Class.lpszMenuName = szAppName;
- Class.lpszClassName = szAppName;
-
- if( ! RegisterClass( &Class ) )
- return FALSE;
-
- } else {
-
- GetInstanceData(
- hPrevInstance, (NPSTR)&hbrBkgd, sizeof(hbrBkgd)
- );
- }
-
- hWndWhat = CreateWindow(
- szAppName,
- szTitle,
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, 0,
- CW_USEDEFAULT, 0,
- (HWND)NULL,
- (HMENU)NULL,
- hInstance,
- (LPSTR)NULL
- );
-
- if( ! hWndWhat )
- return FALSE;
-
- ShowWindow( hWndWhat, nCmdShow );
- UpdateWindow( hWndWhat );
-
- return TRUE;
- }
-
- /* Initializes one scroll bar in the control dialog. */
-
- void SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
- HWND hWndDlg;
- int idBar;
- int nValue;
-
- int nMin;
- int nMax;
- {
- HWND hWndBar;
-
- hWndBar = GetDlgItem( hWndDlg, idBar );
-
- SetScrollRange( hWndBar, SB_CTL, nMin, nMax, FALSE );
- SetScrollPos( hWndBar, SB_CTL, nValue, FALSE );
-
- SetDlgItemInt( hWndDlg, idBar+1, nValue, FALSE );
- }
-
- /* Invalidates the text within the main window, adjusting the
- * text rectangle if it's gone out of bounds.
- */
-
- void SayInvalidateText( hWnd )
- HWND hWnd;
- {
- SayLimitTextPos( hWnd );
- InvalidateRect( hWnd, &rcText, FALSE );
- }
-
- /* Checks the text position against the window client area
- * rectangle. If it's moved off the window in any direction,
- * forces it back inside, and also reverses the ptAdvance value
- * for that direction so it will "bounce" off the edge. Handles
- * both the iconic and open window cases.
- */
-
- void SayLimitTextPos( hWnd )
- HWND hWnd;
- {
- RECT rcClient;
- POINT ptTextSize;
-
- ptTextSize = ptCharSize;
-
- if( ! bIconic ) {
- pText = szText;
- ptTextSize.x *= strlen(szText);
- }
-
- GetClientRect( hWndWhat, &rcClient );
-
- if( rcText.left > rcClient.right - ptTextSize.x ) {
- rcText.left = rcClient.right - ptTextSize.x;
- ptAdvance.x = -ptAdvance.x;
- }
-
- if( rcText.left < rcClient.left ) {
- rcText.left = rcClient.left;
- ptAdvance.x = -ptAdvance.x;
- }
-
- if( rcText.top > rcClient.bottom - ptTextSize.y ) {
- rcText.top = rcClient.bottom - ptTextSize.y;
- ptAdvance.y = -ptAdvance.y;
- }
-
-
- if( rcText.top < rcClient.top ) {
- rcText.top = rcClient.top;
- ptAdvance.y = -ptAdvance.y;
- }
-
- rcText.right = rcText.left + ptTextSize.x;
- rcText.bottom = rcText.top + ptTextSize.y;
- }
-
- /* Moves the text within the window, by invalidating the old
- * position, adjusting rcText according to ptMove, and then
- * invalidating the new position.
- */
-
- void SayMoveText( hWnd, ptMove )
- HWND hWnd;
- POINT ptMove;
- {
- SayInvalidateText( hWnd );
- rcText.left = ptMove.x - (rcText.right - rcText.left >> 1);
- rcText.top = ptMove.y - (rcText.bottom - rcText.top >> 1);
- SayInvalidateText( hWnd );
- }
-
- /* Sets one of the dialog scroll bars to *pnValue. If that
- * value is out of range, limits it to the proper range and
- * forces *pnValue to be within the range as well.
- */
-
- void SaySetBar( hWndDlg, pnValue, idBar )
- HWND hWndDlg;
- int * pnValue;
- int idBar;
- {
- HWND hWndBar;
- int nMin;
- int nMax;
- int nValue;
- BOOL bOK;
-
- hWndBar = GetDlgItem( hWndDlg, idBar );
-
- GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );
-
- nValue = GetDlgItemInt( hWndDlg, idBar+1, &bOK, FALSE );
-
- if( bOK && nValue >= nMin && nValue <= nMax ) {
- *pnValue = nValue;
- SetScrollPos( hWndBar, SB_CTL, nValue, TRUE );
- } else {
- SetDlgItemInt(
- hWndDlg,
- idBar+1,
- GetScrollPos( hWndBar, SB_CTL ),
- FALSE
- );
- }
- }
-
- /* Dialog function for the control panel dialog box. */
-
- BOOL FAR SayWhatDlgProc( hWndDlg, wMsg, wParam, lParam )
- HWND hWndDlg;
- unsigned wMsg;
- WORD wParam;
- LONG lParam;
- {
- HWND hWndBar;
- RECT rcWin;
- int n;
-
- switch( wMsg )
- {
- case WM_COMMAND:
- switch( wParam )
- {
- case IDOK:
- KillTimer( hWndWhat, TIMER_MOVE );
- GetDlgItemText(
- hWndDlg, ITEM_WHAT, szText, sizeof(szText)
- );
- if( strlen(szText) = = 0 )
- LoadString(
- hInstance, STR_WHAT, szText, sizeof(szText)
- );
- pText = szText;
- SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
- SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
- SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
- InvalidateRect( hWndWhat, NULL, FALSE );
- return TRUE;
-
- case IDCANCEL:
- DestroyWindow( hWndDlg );
- return TRUE;
-
- case ITEM_CLEAN:
- case ITEM_FLICKER:
- bCleanPaint =
- IsDlgButtonChecked( hWndDlg, ITEM_CLEAN );
- return TRUE;
- }
- return FALSE;
-
- case WM_HSCROLL:
- if( HIWORD(lParam) )
- SayDoBarMsg(
- hWndDlg, HIWORD(lParam), wParam, LOWORD(lParam)
- );
- return TRUE;
-
- case WM_INITDIALOG:
- GetWindowRect( hWndDlg, &rcWin );
- ClientToScreen( hWndWhat, (LPPOINT)&rcWin.left );
- ClientToScreen( hWndWhat, (LPPOINT)&rcWin.right );
- n = rcWin.right - ptScreenSize.x + ptCharSize.x;
- if( n > 0 )
- rcWin.left -= n;
- rcWin.left &= ~7; /* byte align */
- n = rcWin.bottom - ptScreenSize.y + ptCharSize.y;
- if( n > 0 )
- rcWin.top -= n;
- SetWindowPos(
- hWndDlg,
- (HWND)NULL,
- rcWin.left,
- rcWin.top,
- 0, 0,
- SWP_NOSIZE | SWP_NOZORDER |
- SWP_NOREDRAW | SWP_NOACTIVATE
- );
- SetDlgItemText( hWndDlg, ITEM_WHAT, szText );
- SendDlgItemMessage(
- hWndDlg, ITEM_WHAT,
- EM_LIMITTEXT, sizeof(szText)-1, 0L
- );
- SayInitBar(
- hWndDlg, ITEM_INTBAR,
- nInterval, MIN_INTERVAL, MAX_INTERVAL
- );
-
- SayInitBar(
- hWndDlg, ITEM_DISTBAR,
- nDistance, MIN_DISTANCE, MAX_DISTANCE
- );
- CheckDlgButton( hWndDlg, ITEM_CLEAN, TRUE );
- return TRUE;
-
- case WM_NCDESTROY:
- FreeProcInstance( lpfnDlgProc );
- hWndPanel = NULL;
- return FALSE;
- }
- return FALSE;
- }
-
- /* Painting procedure for the main window. Handles both the
- * clean and flickery painting methods for demonstration
- * purposes.
- */
-
- void SayWhatPaint( hWnd )
- HWND hWnd;
- {
- PAINTSTRUCT ps;
-
- BeginPaint( hWnd, &ps );
-
- SetTextColor( ps.hdc, rgbTextColor );
-
- SayLimitTextPos( hWnd );
-
- if( bCleanPaint ) {
-
- /* Clean painting, avoid redundant erasing */
-
- TextOut(
- ps.hdc,
- rcText.left,
- rcText.top,
- pText,
- bIconic ? 1 : strlen(szText)
- );
-
- SayFillRect(
- ps.hdc,
- ps.rcPaint.left,
- ps.rcPaint.top,
- rcText.left,
- ps.rcPaint.bottom
- );
-
- SayFillRect(
- ps.hdc,
- rcText.left,
- ps.rcPaint.top,
- rcText.right,
- rcText.top
- );
-
- SayFillRect(
- ps.hdc,
- rcText.left,
- rcText.bottom,
- rcText.right,
- ps.rcPaint.bottom
- );
-
- SayFillRect(
- ps.hdc,
- rcText.right,
- ps.rcPaint.top,
- ps.rcPaint.right,
- ps.rcPaint.bottom
- );
-
- } else {
-
- /* Flickery painting, erase background and paint traditionally */
-
- FillRect( ps.hdc, &ps.rcPaint, hbrBkgd );
-
- TextOut(
- ps.hdc,
- rcText.left,
- rcText.top,
- pText,
- bIconic ? 1 : strlen(szText)
- );
-
- }
-
- EndPaint( hWnd, &ps );
-
- if( ! nInterval )
- SayAdvanceTextPos( hWnd );
- }
- /* Window function for the main window. */
-
- LONG FAR SayWhatWndProc( hWnd, wMsg, wParam, lParam )
- HWND hWnd;
- unsigned wMsg;
- WORD wParam;
- LONG lParam;
- {
- FARPROC lpfnAbout;
-
- switch( wMsg )
- {
- case WM_COMMAND:
- switch( wParam )
- {
- case CMD_ABOUT:
- lpfnAbout =
- MakeProcInstance( SayAboutDlgProc, hInstance );
- DialogBox(
- hInstance, MAKEINTRESOURCE(DLG_ABOUT),
- hWnd, lpfnAbout
- );
- FreeProcInstance( lpfnAbout );
- return 0L;
-
- case CMD_EXIT:
- DestroyWindow( hWndWhat );
- return 0L;
-
- case CMD_WHAT:
- if( hWndPanel ) {
- BringWindowToTop( hWndPanel );
- } else {
- lpfnDlgProc =
- MakeProcInstance( SayWhatDlgProc, hInstance );
- if( ! lpfnDlgProc )
- return 0L;
- hWndPanel = CreateDialog(
- hInstance,
- MAKEINTRESOURCE(DLG_WHAT),
- (HWND)NULL,
- lpfnDlgProc
- );
- if( ! hWndPanel )
- FreeProcInstance( lpfnDlgProc );
- }
- }
- break;
-
- case WM_CREATE:
- srand( (int)time(NULL) );
- SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
- return 0L;
-
- case WM_DESTROY:
- if( hWndPanel )
- DestroyWindow( hWndPanel );
- PostQuitMessage( 0 );
- return 0L;
-
- case WM_KEYDOWN:
- SayInvalidateText( hWnd );
- switch( wParam )
- {
- case VK_LEFT:
- rcText.left -= ptCharSize.x;
- ptAdvance.x = -1;
- ptAdvance.y = 0;
- break;
-
- case VK_RIGHT:
- rcText.left += ptCharSize.x;
- ptAdvance.x = 1;
- ptAdvance.y = 0;
- break;
-
- case VK_UP:
- rcText.top -= ptCharSize.y >> 1;
- ptAdvance.x = 0;
- ptAdvance.y = -1;
- break;
-
- case VK_DOWN:
- rcText.top += ptCharSize.y >> 1;
- ptAdvance.x = 0;
- ptAdvance.y = 1;
- break;
-
- default:
- return 0L;
- }
- SayInvalidateText( hWnd );
- nDistLeft = nDistance;
- return 0L;
-
- case WM_LBUTTONDOWN:
- if( bMouseDown )
- break;
- KillTimer( hWnd, TIMER_MOVE );
- bMouseDown = TRUE;
- SetCapture( hWnd );
- SayMoveText( hWnd, MAKEPOINT(lParam) );
- break;
-
- case WM_LBUTTONUP:
- if( ! bMouseDown )
- break;
- bMouseDown = FALSE;
- ReleaseCapture();
- SayMoveText( hWnd, MAKEPOINT(lParam) );
- SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
- break;
-
- case WM_MOUSEMOVE:
- if( bMouseDown )
- SayMoveText( hWnd, MAKEPOINT(lParam) );
- break;
-
- case WM_PAINT:
- SayWhatPaint( hWnd );
- return 0L;
-
- case WM_SIZE:
- if( wParam = = SIZEICONIC ) {
- if( ! bIconic )
- SetTimer( hWnd, TIMER_CHAR, 1000, NULL );
- bIconic = TRUE;
- } else {
- if( bIconic )
- KillTimer( hWnd, TIMER_CHAR );
- bIconic = FALSE;
- }
- SayInvalidateText( hWnd );
- nDistLeft = 0;
- SayAdvanceTextPos( hWnd );
- return 0L;
-
- case WM_TIMER:
- switch( wParam )
- {
- case TIMER_MOVE:
- SayAdvanceTextPos( hWnd );
- break;
-
- case TIMER_CHAR:
- SayAdvanceTextChar( hWnd );
- break;
- }
- return 0L;
- }
- return DefWindowProc( hWnd, wMsg, wParam, lParam );
- }
-
- /* Main function for the application. */
-
- void WinMain( hInst, hPrevInst, lpszCmdLine, nCmdShow )
- HANDLE hInst;
- HANDLE hPrevInst;
- LPSTR lpszCmdLine;
- int nCmdShow;
- {
- MSG msg;
-
- hInstance = hInst;
-
- if( ! SayInitApp( hPrevInst, nCmdShow ) )
- SayExitApp( 1 );
-
- while( GetMessage( &msg, NULL, 0, 0 ) ) {
-
- if( hWndPanel && IsDialogMessage( hWndPanel, &msg ) )
- continue;
-
- TranslateMessage( &msg );
- DispatchMessage( &msg );
- }
-
- SayExitApp( msg.wParam );
- }
-
-
- Figure 6PM: SWP.C is the C Source Code Listing for SAYWHAT
-
- /*
- * SWP.C - C code for SayWhat - Presentation Manager version
- */
-
- #ifndef LINT_ARGS
- #define LINT_ARGS /* turn on argument checking for C runtime */
- #endif
-
- #include <os2pm.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include ╙swp.h╙
-
- #define MIN_INTERVAL 1 /* limits for nInterval */
- #define MAX_INTERVAL 999
-
- #define MIN_DISTANCE 1 /* limits for nDistance */
- #define MAX_DISTANCE 99
-
- #define COLORDATAMAX 5
-
- /* Static variables */
-
- HAB hAB = NULL; /* anchor block handle */
- HMQ hMsgQ = NULL; /* message queue handle */
-
- HWND hWndWhatFrame; /* frame window handle */
- HWND hWndWhat; /* client window handle */
- HWND hWndPanel; /* control panel dialog handle */
-
- CHAR szAppName[10]; /* window class name */
- CHAR szTitle[15]; /* main window title */
-
- CHAR szText[40]; /* current 'what' text */
- PSZ npszText = szText; /* ptr into szText for icon mode */
- CHAR cBlank = ╒ ╒; /* a blank we can point to */
-
- RECT rcText; /* current text rectangle */
- POINT ptAdvance; /* increments for SayAdvanceText */
- POINT ptCharSize; /* X and Y size of a character */
- POINT ptScreenSize; /* X and Y size of the screen */
-
- LONG lColorMax; /* number of available colors */
- LONG lColor; /* current text color index */
-
- SHORT nInterval = 40; /* current 'Interval' setting */
- SHORT nDistance = 30; /* current 'Distance' setting */
- SHORT nDistLeft = 0; /* change direction when hits 0 */
- BOOL bCleanPaint = TRUE; /* clean or flickery painting? */
- BOOL bMouseDown = FALSE; /* is mouse down? */
- BOOL bIconic = FALSE; /* is main window iconic? */
-
- /* Full prototypes for our functions to get type checking */
-
- ULONG FAR PASCAL SayAboutDlgProc( HWND, USHORT, ULONG, ULONG );
- VOID SayAdvanceTextChar( HWND );
- VOID SayAdvanceTextPos( HWND );
- VOID SayChangeColor( HWND );
- VOID SayDoBarMsg( HWND, USHORT, USHORT, SHORT );
- VOID SayExitApp( INT );
- VOID SayFillRect( HPS, SHORT, SHORT, SHORT, SHORT );
- VOID SayInitBar( HWND, SHORT, SHORT, SHORT, SHORT );
- BOOL SayInitApp( VOID );
- VOID SayInvalidateText( HWND );
- VOID SayLimitTextPos( HWND );
- VOID SayMoveText( HWND, POINT );
- VOID SaySetBar( HWND, SHORT *, SHORT );
- ULONG FAR PASCAL SayWhatDlgProc( HWND, USHORT, ULONG, ULONG );
- VOID SayWhatPaint( HWND );
- ULONG FAR PASCAL SayWhatWndProc( HWND, USHORT, ULONG, ULONG );
- void cdecl main( INT, PSZ );
-
- /* Dialog function for the 'About' box */
-
- ULONG FAR PASCAL SayAboutDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
- HWND hWndDlg;
- USHORT wMsg;
- ULONG lParam1;
- ULONG lParam2;
- {
- switch( wMsg )
- {
- case WM_COMMAND:
- switch( LOUSHORT(lParam1) )
- {
- case IDOK:
- WinDismissDlg( hWndDlg, TRUE );
- break;
- }
- }
- return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
- }
-
- /* Advances to next display character in iconic mode.
- * Forces in a blank when it reaches the end of string.
- */
-
- VOID SayAdvanceTextChar( hWnd )
- HWND hWnd;
- {
- if( ! bIconic )
- return;
-
- if( npszText = = &cBlank )
- npszText = szText;
- else if( ! *(++npszText) )
- npszText = &cBlank;
-
- SayChangeColor( hWnd );
- SayInvalidateText( hWnd );
- }
-
- /* Advances text position according to ptAdvance. Decrements
- * nDistLeft first, and when it reaches zero, sets a new
- * randomized ptAdvance and nDistLeft, also changes color.
- * Does nothing if mouse is down, so text will track mouse.
- */
-
- VOID SayAdvanceTextPos( hWnd )
- HWND hWnd;
- {
- SHORT i;
-
- if( bMouseDown )
- return;
-
- SayInvalidateText( hWnd );
-
- if( nDistLeft- < 0 ) {
- nDistLeft = rand() % nDistance + 1;
- do {
- i = rand();
- ptAdvance.x = (
- i < SHRT_MAX/3 ? -1
-
- : i < SHRT_MAX/3*2 ? 0
- : 1
- );
- i = rand();
- ptAdvance.y = (
- i < SHRT_MAX/3 ? -1
- : i < SHRT_MAX/3*2 ? 0
- : 1
- );
- } while( ptAdvance.x = = 0 && ptAdvance.y = = 0 );
- if( ! bIconic )
- SayChangeColor( hWnd );
- } else {
- rcText.xLeft += ptAdvance.x;
- rcText.xRight += ptAdvance.x;
- rcText.yTop += ptAdvance.y;
- rcText.yBottom += ptAdvance.y;
- }
-
- SayInvalidateText( hWnd );
- }
-
- /* Changes color to a random selection, if color is available.
- * Forces a color change - if the random selection is the same
- * as the old one, it tries again.
- */
-
- VOID SayChangeColor( hWnd )
- HWND hWnd;
- {
- HPS hPS;
- LONG lWindow;
- LONG lNew;
-
- hPS = WinGetPS( hWnd );
-
- if( lColorMax <= 2 ) {
- lColor = GpiQueryColorIndex(
- hPS,
- WinQuerySysColor( hAB, SCLR_WINDOWTEXT ),
- 0L
- );
-
- } else {
- lWindow = GpiQueryColorIndex(
- hPS,
- WinQuerySysColor( hAB, SCLR_WINDOW ),
- 0L
- );
- do {
- lNew = rand() % lColorMax;
- } while( lNew = = lWindow || lNew = = lColor );
- lColor = lNew;
- }
-
- WinReleasePS( hPS );
-
- }
-
- /* Handles scroll bar messages from the control dialog box.
- * Adjusts scroll bar position, taking its limits into account,
- * copies the scroll bar value into the adjacent edit control,
- * then sets the nDistance or nInterval variable appropriately.
- */
-
- VOID SayDoBarMsg( hWndDlg, idBar, wCode, nThumb )
- HWND hWndDlg;
- USHORT idBar;
- USHORT wCode;
- SHORT nThumb;
- {
- SHORT nPos;
- SHORT nOldPos;
- ULONG lRange;
-
- nOldPos = nPos =
- (SHORT)WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
- );
-
- lRange =
- WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
- );
-
- switch( wCode ) {
-
- case SB_LINEUP: -nPos; break;
-
- case SB_LINEDOWN: ++nPos; break;
-
- case SB_PAGEUP: nPos -= 10; break;
-
- case SB_PAGEDOWN: nPos += 10; break;
-
- case SB_THUMBPOSITION:
- case SB_THUMBTRACK: nPos = nThumb; break;
-
- case SB_TOP: nPos = LOUSHORT(lRange); break;
-
- case SB_BOTTOM: nPos = HIUSHORT(lRange); break;
-
- }
-
- if( nPos < LOUSHORT(lRange) )
- nPos = LOUSHORT(lRange);
-
- if( nPos > HIUSHORT(lRange) )
- nPos = HIUSHORT(lRange);
-
- if( nPos = = nOldPos )
- return;
-
- WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_SETPOS, (LONG)nPos, 0L
- );
-
- WinSetDlgItemShort( hWndDlg, idBar+1, nPos, FALSE );
-
- switch( idBar )
- {
- case ITEM_DISTBAR:
- nDistance = nPos;
- break;
-
- case ITEM_INTBAR:
- WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
- nInterval = nPos;
- WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );
-
- WinInvalidateRect( hWndWhat, NULL );
- break;
- }
- }
-
- /* Terminates the application, freeing up allocated resources.
- * Note that this function does NOT return to the caller, but
- * exits the program.
- */
-
- VOID SayExitApp( nRet )
- INT nRet;
- {
- if( hWndWhatFrame )
- WinDestroyWindow( hWndWhatFrame );
-
- if( hMsgQ )
- WinDestroyMsgQueue( hMsgQ );
-
- if( hAB )
-
- WinTerminate( hAB );
-
- exit( nRet );
- }
-
- /* Fills a specified rectangle with the background color.
- * Checks that the rectangle is non-empty first.
- */
-
- VOID SayFillRect( hPS, xLeft, xBottom, xRight, xTop )
- HPS hPS;
- SHORT xLeft;
- SHORT xBottom;
- SHORT xRight;
- SHORT xTop;
-
- {
- RECT rcFill;
-
- if( xLeft >= xRight || xBottom >= xTop )
-
- return;
-
- WinSetRect( hAB, &rcFill, xLeft, xBottom, xRight, xTop );
-
- WinFillRect(
- hPS,
- &rcFill,
- WinQuerySysColor( hAB, SCLR_WINDOW )
- );
- }
-
- /* Initializes the application. */
-
- BOOL SayInitApp()
- {
- HPS hPS;
- BOOL bOK;
-
- hAB = WinInitialize();
- if( ! hAB )
-
- return FALSE;
-
- hMsgQ = WinCreateMsgQueue( hAB, 0 );
- if( ! hMsgQ )
- return FALSE;
-
- WinLoadString(
- hAB, NULL, STR_NAME, szAppName, sizeof(szAppName)
- );
- WinLoadString(
- hAB, NULL, STR_TITLE, szTitle, sizeof(szTitle)
- );
- WinLoadString(
- hAB, NULL, STR_WHAT, szText, sizeof(szText)
- );
-
- bOK = WinRegisterClass(
- hAB,
- szAppName,
- (LPFNWP)SayWhatWndProc,
- CS_SYNCPAINT,
- 0,
-
- NULL
- );
- if( ! bOK )
- return FALSE;
-
- hWndWhatFrame = WinCreateStdWindow(
- (HWND)NULL,
- FS_TITLEBAR | FS_SYSMENU |
- FS_MENU | FS_MINMAX | FS_SIZEBORDER,
- szAppName,
- szTitle,
- 0L,
- (HMODULE)NULL,
- MENU_WHAT,
- &hWndWhat
- );
-
- if( ! hWndWhatFrame )
- return FALSE;
-
- WinShowWindow( hWndWhat, TRUE );
-
- return TRUE;
- }
-
- /* Initializes one scroll bar in the control dialog. */
-
- VOID SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
- HWND hWndDlg;
- SHORT idBar;
- SHORT nValue;
- SHORT nMin;
-
- SHORT nMax;
- {
- HWND hWndBar;
-
- hWndBar = WinWindowFromID( hWndDlg, idBar );
-
- WinSendDlgItemMsg(
- hWndDlg,
- idBar,
- SBM_SETSCROLLBAR,
-
- (LONG)nValue,
- MAKELONG( nMin, nMax )
- );
-
- WinSetDlgItemShort( hWndDlg, idBar+1, nValue, FALSE );
- }
-
- /* Invalidates the text within the main window, adjusting the
- * text rectangle if it's gone out of bounds.
- */
-
- VOID SayInvalidateText( hWnd )
- HWND hWnd;
- {
- SayLimitTextPos( hWnd );
- WinInvalidateRect( hWnd, &rcText );
- }
-
- /* Checks the text position against the window client area
- * rectangle. If it's moved off the window in any direction,
- * forces it back inside, and also reverses the ptAdvance value
- * for that direction so it will 'bounce' off the edge. Handles
- * both the iconic and open window cases.
- */
-
- VOID SayLimitTextPos( hWnd )
- HWND hWnd;
- {
- RECT rcClient;
- POINT ptTextSize;
-
- ptTextSize = ptCharSize;
-
- if( ! bIconic ) {
- npszText = szText;
- ptTextSize.x *= strlen(szText);
- }
-
- WinQueryWindowRect( hWndWhat, &rcClient );
-
- if( rcText.xLeft > rcClient.xRight - ptTextSize.x ) {
- rcText.xLeft = rcClient.xRight - ptTextSize.x;
- ptAdvance.x = -ptAdvance.x;
- }
-
- if( rcText.xLeft < rcClient.xLeft ) {
- rcText.xLeft = rcClient.xLeft;
- ptAdvance.x = -ptAdvance.x;
- }
-
- if( rcText.yBottom < rcClient.yBottom ) {
- rcText.yBottom = rcClient.yBottom;
- ptAdvance.y = -ptAdvance.y;
- }
-
- if( rcText.yBottom > rcClient.yTop - ptTextSize.y ) {
- rcText.yBottom = rcClient.yTop - ptTextSize.y;
- ptAdvance.y = -ptAdvance.y;
- }
-
- rcText.xRight = rcText.xLeft + ptTextSize.x;
- rcText.yTop = rcText.yBottom + ptTextSize.y;
-
- }
-
- /* Moves the text within the window, by invalidating the old
- * position, adjusting rcText according to ptMove, and then
- * invalidating the new position.
- */
-
- VOID SayMoveText( hWnd, ptMove )
- HWND hWnd;
- POINT ptMove;
- {
- SayInvalidateText( hWnd );
- rcText.xLeft =
- ptMove.x - (rcText.xRight - rcText.xLeft >> 1);
- rcText.yBottom =
- ptMove.y - (rcText.yTop - rcText.yBottom >> 1);
- SayInvalidateText( hWnd );
- }
-
- /* Sets one of the dialog scroll bars to *pnValue. If that value
- * is out of range, limits it to the proper range and forces
- * *pnValue to be within the range as well.
- */
-
- VOID SaySetBar( hWndDlg, pnValue, idBar )
- HWND hWndDlg;
- SHORT * pnValue;
- SHORT idBar;
- {
- ULONG lRange;
- SHORT nValue;
- BOOL bOK;
-
- lRange =
- WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
- );
-
- nValue =
- WinQueryDlgItemShort( hWndDlg, idBar+1, &bOK, FALSE );
-
- if(
- bOK &&
- nValue >= LOUSHORT(lRange) &&
- nValue <= HIUSHORT(lRange)
- ) {
- *pnValue = nValue;
- WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_SETPOS, (LONG)nValue, (LONG)TRUE
- );
- } else {
- WinSetDlgItemShort(
- hWndDlg,
- idBar + 1,
- (INT)WinSendDlgItemMsg(
- hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
- ),
- FALSE
- );
- }
- }
-
- /* Dialog function for the control panel dialog box. */
-
- ULONG FAR PASCAL SayWhatDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
- HWND hWndDlg;
- USHORT wMsg;
- ULONG lParam1;
- ULONG lParam2;
- {
- HWND hWndBar;
- RECT rcWin;
- SHORT n;
-
- switch( wMsg )
- {
- case WM_COMMAND:
- switch( LOUSHORT(lParam1) )
- {
- case IDOK:
- WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
- WinQueryWindowText(
-
- WinWindowFromID( hWndDlg, ITEM_WHAT ),
- sizeof(szText),
- szText
- );
- if( strlen(szText) = = 0 )
- WinLoadString(
- hAB, NULL, STR_WHAT, szText, sizeof(szText)
- );
- npszText = szText;
- SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
- SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
- WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );
- WinInvalidateRect( hWndWhat, NULL );
- break;
-
- case IDCANCEL:
- WinDestroyWindow( hWndDlg );
- break;
-
- case ITEM_CLEAN:
- case ITEM_FLICKER:
- bCleanPaint = (BOOL)WinSendDlgItemMsg(
-
- hWndDlg, ITEM_CLEAN,
- BM_QUERYCHECK, 0L, 0L
- );
- break;
- }
- break;
-
- case WM_DESTROY:
- hWndPanel = NULL;
- break;
-
- case WM_HSCROLL:
- SayDoBarMsg(
- hWndDlg, LOUSHORT(lParam1),
- HIUSHORT(lParam2), LOUSHORT(lParam2)
- );
- break;
-
- case WM_INITDLG:
- WinQueryWindowRect( hWndDlg, &rcWin );
- WinMapWindowPoints(
- hWndWhat, (HWND)NULL, (LPPOINT)&rcWin.xLeft, 2
-
- );
- n = rcWin.xRight - ptScreenSize.x + ptCharSize.x;
- if( n > 0 )
- rcWin.xLeft -= n;
- rcWin.xLeft &= ~7; /* byte align */
- n = rcWin.yTop - ptScreenSize.y + ptCharSize.y;
- if( n > 0 )
- rcWin.yBottom -= n;
- WinSetWindowPos(
- hWndDlg,
- (HWND)NULL,
- rcWin.xLeft,
- rcWin.yBottom,
- 0, 0,
- SWP_MOVE
- );
- WinSetWindowText(
- WinWindowFromID( hWndDlg, ITEM_WHAT ), szText
- );
- WinSendDlgItemMsg(
- hWndDlg, ITEM_WHAT, EM_SETTEXTLIMIT,
- (LONG)sizeof(szText)-1, 0L
- );
- SayInitBar(
- hWndDlg, ITEM_INTBAR, nInterval,
- MIN_INTERVAL, MAX_INTERVAL
- );
- SayInitBar(
- hWndDlg, ITEM_DISTBAR, nDistance,
- MIN_DISTANCE, MAX_DISTANCE
- );
- WinSendDlgItemMsg(
- hWndDlg, ITEM_CLEAN, BM_SETCHECK, (LONG)TRUE, 0L
- );
- break;
- }
- return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
- }
-
- /* Painting procedure for the main window. Handles both the
- * clean and flickery painting methods for demonstration
- * purposes.
- */
-
- VOID SayWhatPaint( hWnd )
- HWND hWnd;
- {
- HPS hPS;
- RECT rcPaint;
- GPOINT gptText;
-
- hPS = WinBeginPaint( hWnd, (HPS)NULL, &rcPaint );
-
- GpiSetColor( hPS, lColor );
-
- SayLimitTextPos( hWnd );
-
- gptText.x = (LONG)rcText.xLeft;
- gptText.y = (LONG)rcText.yBottom;
-
- if( bCleanPaint ) {
-
- /* Clean painting, avoid redundant erasing */
-
- GpiSetBackMix( hPS, MIX_OVERPAINT );
-
- GpiCharStringAt(
- hPS,
- &gptText,
- (LONG)( bIconic ? 1 : strlen(szText) ),
- npszText
- );
-
- SayFillRect(
- hPS,
- rcPaint.xLeft,
- rcPaint.yBottom,
- rcText.xLeft,
- rcPaint.yTop
- );
-
- SayFillRect(
- hPS,
- rcText.xLeft,
- rcText.yTop,
- rcText.xRight,
- rcPaint.yTop
- );
-
- SayFillRect(
- hPS,
- rcText.xLeft,
- rcPaint.yBottom,
- rcText.xRight,
- rcText.yBottom
- );
-
- SayFillRect(
- hPS,
- rcText.xRight,
- rcPaint.yBottom,
- rcPaint.yRight,
- rcPaint.yTop
- );
-
- } else {
-
- /* Flickery painting, erase background and paint traditionally */
-
- WinFillRect(
- hPS,
- &rcPaint,
- WinQuerySysColor( hAB, SCLR_WINDOW )
- );
-
- GpiCharStringAt(
- hPS,
- &gptText,
- (LONG)( bIconic ? 1 : strlen(szText) ),
- npszText
- );
- }
-
- WinEndPaint( hPS );
-
- if( ! nInterval )
- SayInvalidateText( hWnd );
- }
-
- /* Window function for the main window. */
-
- ULONG FAR PASCAL SayWhatWndProc( hWnd, wMsg, lParam1, lParam2 )
- HWND hWnd;
- USHORT wMsg;
- ULONG lParam1;
- ULONG lParam2;
- {
- POINT ptMouse;
- GSIZE gsChar;
- HPS hPS;
- LONG ColorData[COLORDATAMAX];
- BOOL bNowIconic;
-
- switch( wMsg )
- {
- case WM_BUTTON1DOWN:
- if( bMouseDown )
- break;
- WinStopTimer( hAB, hWnd, TIMER_MOVE );
-
- bMouseDown = TRUE;
- WinSetCapture( hAB, hWnd );
- ptMouse.x = LOUSHORT(lParam1);
- ptMouse.y = HIUSHORT(lParam1);
- SayMoveText( hWnd, ptMouse );
- return 0L;
-
- case WM_BUTTON1UP:
- if( ! bMouseDown )
- break;
- bMouseDown = FALSE;
- WinSetCapture( hAB, (HWND)NULL );
- ptMouse.x = LOUSHORT(lParam1);
- ptMouse.y = HIUSHORT(lParam1);
- SayMoveText( hWnd, ptMouse );
- WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
- return 0L;
-
- case WM_CHAR:
- if(
- ( LOUSHORT(lParam1) & KC_KEYUP ) ||
- ! ( LOUSHORT(lParam1) & KC_VIRTUALKEY )
-
- ) {
- break;
- }
- SayInvalidateText( hWnd );
- switch( HIUSHORT(lParam2) )
- {
- case VK_LEFT:
- rcText.xLeft -= ptCharSize.x;
- ptAdvance.x = -1;
- ptAdvance.y = 0;
- break;
- case VK_RIGHT:
- rcText.xLeft += ptCharSize.x;
- ptAdvance.x = 1;
- ptAdvance.y = 0;
- break;
- case VK_UP:
- rcText.yBottom -= ptCharSize.y >> 1;
- ptAdvance.x = 0;
- ptAdvance.y = -1;
- break;
- case VK_DOWN:
-
- rcText.yBottom += ptCharSize.y >> 1;
- ptAdvance.x = 0;
- ptAdvance.y = 1;
- break;
- default:
- return 0L;
- }
- SayInvalidateText( hWnd );
- nDistLeft = nDistance;
- return 0L;
-
- case WM_COMMAND:
- switch( LOUSHORT(lParam1) )
- {
- case CMD_ABOUT:
- WinDlgBox(
- (HWND)NULL, hWnd, (LPFNWP)SayAboutDlgProc,
- NULL, DLG_ABOUT, NULL
- );
- return 0L;
-
- case CMD_EXIT:
-
- WinDestroyWindow( hWndWhatFrame );
- return 0L;
-
- case CMD_WHAT:
- if( hWndPanel ) {
- WinSetWindowPos(
- hWndPanel,
- HWND_TOP,
- 0, 0, 0, 0,
- SWP_ZORDER | SWP_ACTIVATE
- );
- } else {
- hWndPanel = WinLoadDlg(
- (HWND)NULL,
- (HWND)NULL,
- (LPFNWP)SayWhatDlgProc,
- NULL,
- DLG_WHAT,
- NULL
- );
- }
- }
-
- return 0L;
-
- case WM_CREATE:
- /* find out character/screen sizes, number of colors */
- hPS = WinGetPS( hWnd );
- GpiQueryCharBox( hPS, &gsChar );
- GpiQueryColorData( hPS, (LONG)COLORDATAMAX, ColorData );
- WinReleasePS( hPS );
- lColorMax = ColorData[3];
- ptCharSize.x = gsChar.width;
- ptCharSize.y = gsChar.height;
- ptScreenSize.x =
- WinQuerySysValue( (HWND)NULL, SV_CXSCREEN );
- ptScreenSize.y =
- WinQuerySysValue( (HWND)NULL, SV_CYSCREEN );
- /* initialize timer */
- srand( (INT)time(NULL) );
- WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
- return 0L;
-
- case WM_DESTROY:
- if( hWndPanel )
-
- WinDestroyWindow( hWndPanel );
- WinPostQueueMsg( hMsgQ, WM_QUIT, 0L, 0L );
- return 0L;
-
- case WM_ERASEBACKGROUND:
- return 1L; /* don't erase */
-
- case WM_MINMAX:
- bNowIconic = ( LOUSHORT(lParam1) = = SWP_MINIMIZE );
- if( bIconic != bNowIconic ) {
- bIconic = bNowIconic;
- if( bIconic )
- WinStopTimer( hAB, hWnd, TIMER_CHAR );
- else
- WinStartTimer( hAB, hWnd, TIMER_CHAR, 1000 );
- }
- return 1L;
-
- case WM_MOUSEMOVE:
- if( bMouseDown ) {
- ptMouse.x = LOUSHORT(lParam1);
- ptMouse.y = HIUSHORT(lParam1);
-
- SayMoveText( hWnd, ptMouse );
- }
- return 0L;
-
- case WM_PAINT:
- SayWhatPaint( hWnd );
- return 0L;
-
- case WM_SIZE:
- SayInvalidateText( hWnd );
- nDistLeft = 0;
- SayAdvanceTextPos( hWnd );
- return 0L;
-
- case WM_TIMER:
- switch( LOUSHORT(lParam1) ) {
- case TIMER_MOVE:
- SayAdvanceTextPos( hWnd );
- break;
- case TIMER_CHAR:
- SayAdvanceTextChar( hWnd );
- break;
-
- }
- return 0L;
- }
- return WinDefWindowProc( hWnd, wMsg, lParam1, lParam2 );
- }
-
- /* Main function for the application. */
-
- void cdecl main( nArgs, pArgs )
- INT nArgs;
- PSZ pArgs;
- {
- QMSG qMsg;
-
- if( ! SayInitApp() )
- SayExitApp( 1 );
-
- while( WinGetMsg( hAB, &qMsg, (HWND)NULL, 0, 0 ) ) {
-
- if( hWndPanel && WinProcessDlgMsg( hWndPanel, &qMsg ) )
-
- continue;
-
- WinDispatchMsg( hAB, &qMsg );
- }
-
- SayExitApp( 0 );
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- Programming Considerations in Porting to Microsoft XENIX System V/386
-
- ───────────────────────────────────────────────────────────────────────────
- Also see the related article:
- Demand Paging and Virtual Memory
- ───────────────────────────────────────────────────────────────────────────
-
- Martin Dunsmuir
-
- XENIX(R) System V/386 is the Microsoft version of the UNIX(R) Operating
- System ported to the Intel(R) 386 microprocessor. The product, which was
- released to OEMs in the summer of 1987, is the first from Microsoft to take
- full advantage of the features of the Intel 386 microprocessor. In
- particular, XENIX allows 32-bit applications to be written for the first
- time.
-
- Microsoft has been active in the UNIX business since its inception, and in
- fact, XENIX predates even MS-DOS(R) as a Microsoft product. Since 1983, the
- XENIX development effort has concentrated on the Intel microprocessors──the
- 286 introduced by Intel in 1983 and more recently the 386. By concentrating
- on one instruction set and chip architecture, Microsoft has been able to
- develop the world's largest installed base of binary-compatible UNIX
- systems. Currently, XENIX accounts for about 70 percent of all UNIX
- licenses sold, or roughly 250,000 units, and more than 1000 different
- applications are available. Although these numbers may sound small when
- compared with the numbers quoted for MS-DOS, the majority of these systems
- are used in multiuser configurations supporting between 2 and 16 users.
-
- Most of the current XENIX installed base is on 286-based PCs such as the
- IBM(R) PC AT(R). XENIX is used primarily as a cost-effective solution for
- small businesses that require multiuser access; it has sold particularly
- well in vertical markets that lend themselves to a customized "systems
- solution." Two such markets are dentistry and accounting.
-
- There was great excitement when Intel informed Microsoft that it was
- planning the 386 chip-and Microsoft set out to find a way to take advantage
- of the chip's features within the XENIX environment. In particular,
- Microsoft wanted to give developers the ability to create 32-bit
- applications and provide support within the operating system for virtual
- memory and demand paging──features that can greatly increase the throughput
- of a computer system.
-
- Another major design goal was to be sure that existing XENIX 286 users were
- not prevented from moving up to the 386. Microsoft wanted its installed base
- to be able to take advantage of the increased performance without having to
- buy new versions of their applications. Since the 386 chip supports both 16-
- and 32-bit segments in its architecture, Microsoft has been able to create
- an environment in which both 16- and 32-bit programs can be executed
- simultaneously. The features of demand paging and virtual memory are
- available transparently to both 16- and 32-bit applications.
-
- Implementing support for segments independently of the paging subsystem
- provides full performance for old applications without slowing down the
- execution of the new 32-bit programs (see Figure 1).
-
- Support for 32-bit applications is the key to the continued success of
- XENIX. It opens the door to the creation of much more powerful programs, as
- well as making it easier for developers to move existing UNIX applications
- onto XENIX. UNIX 32-bit programs being ported to XENIX 286 often needed
- extensive rewriting to make them work well in a 16-bit environment. This
- article outlines the considerations that will help programmers in choosing
- between 16- and 32-bit program models when developing applications or
- migrating to XENIX System V/386.
-
-
- Small-Model Programs
-
- In 16-bit mode, as used on XENIX 286, a program is composed of two segments
- up to 64Kb in size. One segment contains program code; the other segment
- contains data (the stack is of fixed size and resides in the data segment).
- An example of a small-model program is shown in Figure 2.
-
- The program in Figure 2 has only one data and one code segment, so it is
- not necessary to change the contents of the segment registers while the
- program is running. At load time, the operating system initializes them
- to point to the memory in which the program executes (via the LDT). In
- particular, the compiler or assembler programmer does not have to take
- account of changing segment registers during the program execution.
-
- Also of note is the fact that in the small model, both integers and pointers
- to data objects are 16-bit quantities and therefore interchangeable. This is
- important because many programs implicitly make this assumption.
- Unfortunately, although many commands and utilities are less than 64Kb in
- size, most third-party applications are larger than this; they require
- multiple code and/or data segment support. Figure 3 shows a multisegment
- program.
-
-
- Large- and Middle-Model
-
- Programs can overflow the 64Kb limit with their code or their data, or both
- at the same time. Programs that exceed 64Kb of code but still have less than
- 64Kb of data are called middle-model programs. Large-model programs exceed
- 64Kb in both their code and data segments.
-
-
- Large Code
-
- The program is broken down by the linker into pieces that will fit into
- 64Kb. If a program's code exceeds 64Kb, then it is necessary to place
- different parts of it in different segments. (Because the breaks occur on
- function boundaries, each piece can be less than 64Kb in size.) The compiler
- must also gener-ate code that automatically reloads the CS register when
- the thread of execution moves from one segment to another. This results in a
- program that is slightly slower than the equivalent small-model image. The
- effect is not too drastic because within each subroutine CS remains constant
- so the frequency of segment register reloads is relatively low in
- comparison with the number of instructions executed.
-
-
- Large Data
-
- Data structures are spread among a number of different segments when data
- exceeds 64Kb. Again, the linker fills each segment with data structures as
- fully as possible. The performance penalty to be paid for going to large
- data is much larger than in the case of code because the frequency of
- intersegment data accesses is generally much greater than that of
- intersegment branches. The C compiler does not know which segment a
- particular data structure resides in at compile time; this causes a large
- number of unnecessary intersegment calls to be generated. Another problem
- in moving to large model is the fact that the size of data pointers
- increases to 32 bits. This means that the size of an integer is no longer
- equal to the size of a pointer, and programs that rely on this equality,
- either implicitly or explicitly, break. This is one of the primary problems
- developers experience when porting existing 32-bit UNIX programs to XENIX
- 286. A summary of the different models can be seen in Figure 4.
-
-
- Hybrid Model
-
- Using the 286, where 16-bit segments are the norm but most useful
- applications exceed 64Kb in size, it is important for programmers to
- understand how to design their programs to reduce the effect of the
- multiple segment accesses. One way of doing this is to select specific data
- structures to be placed in separate far segments, while keeping
- indiscriminately accessed data structures (and the stack if possible) in a
- single near segment. The code generated by the Microsoft(R) C compiler in
- this case is much more efficient because the far keyword, used to mark
- specific data structures, gives the compiler a hint as to when it should
- reload segment registers.
-
- Use of a hybrid model with carefully designed programs comes close to the
- performance of small-model programs, even though they exceed 64Kb in size.
- However, there is a down side to this approach──it makes programs inherently
- less portable between XENIX 286 and other UNIX environments. Also,
- converting an existing 32-bit UNIX application to a hybrid model is
- complicated by the differences in pointer and integer size that make large-
- model ports such a problem.
-
-
- 32-Bit Programming
-
- In contrast to the complexity of a multisegment 286 program, the native 386
- program structure is very simple (see Figure 5). Each program consists of
- one code segment along with one data segment, and each segment can be very
- large in size. (The exact limit depends on the availability of real
- memory and swap space and is typically a few megabytes).
-
- Because the address space is large, it is not necessary to support multiple
- segments in 32-bit mode, either in the operating system or in the C
- compiler. When a program is loaded, all the segment registers are
- initialized to static values and remain unchanged while the program is
- executing. In 32-bit mode, the stack lives in the data segment and grows
- down to lower addresses, while the data segment extends upward to higher
- addresses.
-
- XENIX 386 programs are truly 32-bit; they support 32-bit integers and all
- pointers are 32 bits in length. This eases the problems of porting existing
- 32-bit applications to XENIX 386 in 32-bit mode.
-
- Other advantages offered by the 32-bit mode of the 386 are more orthogonal
- registers and addressing modes, which allow better code generation and more
- register variables, plus extra instructions that improve in-line code
- generation. Thirty-two-bit programs generally exhibit a significant
- performance advantage over the 16-bit versions.
-
-
- New XENIX Applications
-
- The introduction of XENIX 386 no longer constrains the developer to the 16-
- bit architecture. If he chooses, he can develop his application in 32-bit
- mode. However, the choice between a 16- and a 32-bit architecture for a
- new application is not as simple as it appears at first glance. 32-bit
- programs will only execute on XENIX 386, whereas 16-bit applications will
- execute on both XENIX 286 and XENIX 386. The installed base of XENIX 386 is
- still small, but it is almost certain to exceed that of XENIX 286 in time. A
- 16-bit application may be a better choice for developers who want to address
- the largest possible installed base. Let's look at the trade-offs that must
- be considered when making the choice between 16 and 32 bits.
-
- The developer should ask himself the following questions:
-
- ■ What is the size of the application, both code and data?
-
- ■ Is the application an existing UNIX program being ported to XENIX?
-
- ■ Is the application an existing MS-DOS program or aimed at the MS-DOS,
- OS/2, and UNIX markets?
-
- ■ For new applications, what is the target market for the application?
- Is it limited to XENIX or does it have wider appeal in other UNIX or
- DOS markets?
-
- ■ What are the application's performance requirements?
-
-
- Application Size
-
- In many ways size is the most important consideration; unfortunately for
- new applications it is most likely the hardest to answer. For a simple
- application it is probably wise to build the application first as a 32-
- bit program and then see if it will fit into 16 bits. At this point it
- should be remembered that large data is a much more serious performance
- limitation to 16-bit programs-programs with more than 16 bits of code but
- less than 16 bits of data can be built as 16-bit middle-model programs
- without serious performance degradation.
-
- Another approach that can be used to fit a more complex program into the
- 64Kb address space is to break it down into a number of separate,
- communicating processes, each of which fits into the smaller address
- space. Not all programs are amenable to such an architecture. Breaking an
- application into pieces can also limit portability into the MS-DOS
- world.
-
-
- Portability
-
- Many developers of UNIX applications for UNIX systems other than XENIX 286
- have programs that are designed implicitly for the 32-bit world. This is
- because XENIX 286 is one of the few UNIX systems to run on a 16-bit
- processor. Even if size is not a consideration, the work required to port
- UNIX applications from 32 to 16 bits has often deterred developers from
- doing the port.
-
- Such developers are best advised to build their applications for XENIX
- 386 only. Debugging those problems summarized earlier in this article is
- often too great an effort to warrant porting a program to XENIX 286. This
- extra development effort can be considered for a later release if market
- pressure is felt.
-
- When porting existing MS-DOS applications to UNIX, it is usually more
- feasible to build an application in 16 bits. This is certainly the best and
- easiest option if the application contains a significant amount of
- assembler code. Since the XENIX and MS-DOS macro assemblers accept the same
- source syntax in 16-bit mode, assembly code that is not environment-specific
- should port directly to XENIX.
-
- Traditionally, UNIX and MS-DOS applications markets have been separated by a
- wide gap in complexity. This is because the architecture of real-mode MS-
- DOS programs is very different from UNIX. With the advent of OS/2, the
- underlying support provided by the two operating systems is now comparable,
- so it may make sense for new applications to be developed that can easily
- be hosted in both XENIX and OS/2 environments. If this is the case, it makes
- more sense to build the application for the 16-bit environment common to
- both XENIX and OS/2 and to delay the development of a 32-bit application
- until a 32-bit version of OS/2 becomes available.
-
- Another consideration for simpler applications is the use of the C library
- calls supported by the Microsoft C Compiler under MS-DOS. These calls, which
- embody a subset of the UNIX C library calls, can make it relatively easy to
- build a program that can be simply rehosted in both XENIX and MS-DOS
- environments. A good example of this approach would be the Microsoft 286 C
- compiler itself, which is recompiled and linked with different run-time
- libraries for execution on MS-DOS or XENIX 286. The task of creating a
- common source code for both MS-DOS and XENIX versions of the compiler is
- greatly facilitated by the fact that the XENIX and MS-DOS linkers both
- accept the same relocatable format as input (although they generate a
- different executable file format).
-
-
- XENIX and UNIX Markets
-
- Building applications that port easily between XENIX 286 and other UNIX
- platforms has generally been difficult. It is prudent──if a source portable
- application is desired──to remain within the 32-bit world. The 32-bit XENIX
- 386 environment is completely compatible with the System V Interface
- Definition (SVID), and thus there should be very little difficulty in moving
- a carefully designed program from XENIX 386 to other UNIX platforms.
-
-
- Performance
-
- Although performance is a combination of many factors, it is most strongly
- linked to the architecture of the program and to the inherent speed of the
- host computer. All architectural considerations being equal, a 32-bit
- program will execute faster than a 16-bit program on the same 386 CPU.
- Applications that are being ported from the earlier 286 or 8086 worlds onto
- the 386 will experience an increase in raw 16-bit performance, simply by
- running the code on a 386, that more than offsets the need to consider
- rehosting into 32-bit mode.
-
- For new XENIX applications, especially those being ported from other 32-bit
- processors, where a 16-bit port is a serious possibility, it is important to
- understand the performance degradation seen on the 386 between 16-bit and
- 32-bit code. The operating system itself runs in 32-bit mode, and some part
- of a program's execution time is spent in this code. The decrease in speed
- when moving to 16 bits is not as great as a simple comparison of CPU-bound
- 16 and 32 performance might indicate. Figure 8 shows the relative
- execution times of two small C programs, "Cpubound" and "IObound," built
- as small-16, middle-16, large-16, and small-32 programs on XENIX 386.
- Figures 6 and 7 show the source code of these programs .
-
- An analysis of Figure 8 shows that the 32-bit architecture offers a
- significant performance advantage for CPU-bound programs that do a mix of
- arithmetic, pointer processing, and function calls. There is no
- performance difference among the various 16- and 32-bit models chosen for
- I/O-bound activities where the processing is all within the kernel.
- Although the performance of a 16-bit application on XENIX 386 falls short
- of the 32-bit performance, it is still between two and three times greater
- than the performance when that program is run on an 8-Mhz 286. The
- difference in performance between the 386 host and the 286 target must be
- factored in when measuring 16-bit performance on XENIX 386.
-
-
- Conclusions
-
- When designing an application for the XENIX 386 environment, the developer
- must weigh a number of conflicting criteria. The foremost problem is
- whether to build the program in 16- or 32-bit mode. Further questions must
- address the intended market as well as the performance and portability
- required of the completed product. Lastly, it is important to consider
- future compatibility requirements.
-
- Microsoft and AT&T are currently working together to merge XENIX 386 and
- AT&T's UNIX System V/386 Release 3.2 into a single UNIX system that will be
- marketed jointly by the two companies. This Merged Product (MP) will
- support all the existing 286 and 386 executable formats common to UNIX and
- XENIX on the 386, thereby allowing all existing applications to run.
-
- The emphasis for developers using the Merged Product will be to establish
- the UNIX/386, 32-bit mode program interface as the standard for new
- applications. This standard will be a superset of the current XENIX System
- V/386 program interface, without the support for XENIX-specific system call
- extensions. This means that in the long run there will be one binary
- standard, developed and supported by Microsoft and AT&T, which will run on
- all 386 machines running UNIX, thereby stabilizing the market.
-
- Developers who would like their programs to be source compatible with the
- new binary standard may want to avoid the use of XENIX system call
- extensions before the Merged Product becomes available in mid-1988. This
- applies particularly to the use of 32-bit applications (see Figure 9).
- Although kernel support is provided for XENIX extensions in the MP, minimal
- development tools will be provided. Debugging support will be limited to
- the UNIX System V/386 Release 3.2 binary standard. Without exception, the
- functionality of the XENIX call extensions is supported within the
- framework of the UNIX program interface.
-
-
- Figure 1: Page table entries are maintained in groups of 16. This allows a
- 286 segment to expand to 64Kb without existing table entries.
-
- ░░░░░░░░░░░░░░░░Mapping 286 Programs under XENIX 386░░░░░░░░░░░░░░░░░░
-
- Data Page Table
- ╔══════════════════════════╗
- Selector ║ Available for Expansion ║
- LDT ╟──────────────────────────╢
- ╔════════════════╗ ╟──────────────────────────╢
- 5FH ║ DS #2 ║ ║ 32Kb ║- 8 pages
- ╟────────────────╢ ╟──────────────────────────╢ mapped
- 57H ║ DS #1 ║────────────║ 64Kb ║-16 pages
- ╟────────────────╢ ╚══════════════════════════╝ mapped
- 4FH ║ TS #3 ║ Text Page Table
- ╟────────────────╢ ╔══════════════════════════╗
- 47H ║ TS #2 ║─────┐ ║ Unused ║
- ╟────────────────╢ │ ╟──────────────────────────╢
- 3FH ║ TS #1 ║──┐ │ ║ Unused ║
- ╚════════════════╝ │ │ ╟──────────────────────────╢
- │ │ ║ 8Kb ║- 2 pages
- │ │ ╟──────────────────────────╢ mapped
- │ └──────║ 64Kb ║-16 pages
- │ ╟──────────────────────────╢ mapped
- └─────────║ 64Kb ║-16 pages
- ╚══════════════════════════╝ mapped
-
-
- Figure 2: A Small-Model 286 Program
-
- ┌────────────────────────────────────────────────────────┐
- │ Text Segment Data Segment │█
- │ ╔═════════════════════╗64Kb╔═════════════════════╗64Kb│█
- │ ║ ║ ║ ║ │█
- │ ║ Unused ║ ║ Available heap ║ │█
- │ ║ ║ ║ ║ │█
- │ ╟─────────────────────╢ ╟─────────────────────╢ │█
- │ ║ ║ ║ ║ │█
- │ ║ ║ ║ heap (BSS) ║ │█
- │ ║ ║ ║ ║ │█
- │ ║ Text ║ ╟─────────────────────╢ │█
- │ ║ (fixed size) ║ ║ ║ │█
- │ ║ ║ ║ Fixed stack ║ │█
- │ ║ ║ ║ ║ │█
- │ ║ ║ ╟─────────────────────╢ │█
- │ ║ ║ ║ ║ │█
- │ ║ ║ ║ Initialized data ║ │█
- │ ║ ║ ║ ║ │█
- │ ║ ║ ║ ║ │█
- │ ║ ║ ║ ║ │█
- │ ╚═════════════════════╝0 ╚═════════════════════╝0 │█
- │ Selector 3FH Selector 47H │█
- └────────────────────────────────────────────────────────┘█
- █████████████████████████████████████████████████████████
-
-
- Figure 3: Large-Model 286 Program Layout
-
- ╔════════════════╗ ╔════════════════╗
- ║ Unused ║ ║ ║
- ╟────────────────╢ ║ 1st segment ║
- ║ TS #3 ║ ║ available for ║
- ║ 8Kb ║ ║ heap ║
- 4FH╚════════════════╝ 67H╚════════════════╝
-
- ╔════════════════╗ ╔════════════════╗
- ║ ║ ║ ║
- ║ TS #2 ║ ║ DS #2 ║
- ║ 64Kb ║ ║ far data ║
- ║ ║ ║ ║
- 47H╚════════════════╝ 5FH╚════════════════╝
-
- ╔════════════════╗ ╔════════════════╗
- ║ ║ ║ ║
- ║ TS #1 ║ ║ DS #1 ║
- ║ 64Kb ║ ║ stack & data ║
- ║ ║ ║ ║
- 3FH╚════════════════╝ 57H╚════════════════╝
-
-
- Figure 4: Camparison of 286 Program Models
-
- Name of
- Model Max. Text Max. Data Stack Heap Performance
-
- Small <=64Kb <=64Kb Fixed (<64Kb) In Data (<64Kb) Best
-
- Middle >64Kb <=64Kb Fixed In Data (<64Kb) Good
-
- Compact <=64Kb >64Kb <=64Kb >64Kb Poor
-
- Large >64Kb >64Kb <=64Kb >64Kb Poorest
-
- Hybrid
- Data <=64Kb >64Kb Fixed (<64Kb) <64Kb Good
-
-
- Figure 5: Program Layout in 386 Mode
-
- ┌─────────────────────────────────────────────────────┐
- │ Text Segment Data Segment │█
- The stack │ ╔═════════════════╗4Gb ╔═════════════════╗4Gb │█
- grows down │ ║ ║ ║ Video RAM ║virtual │█
- to 0 virtual, │ ║ ║ ╟─────────────────╢ │█
- while the heap │ ║ ║ ║ Unused ║ │█
- grows up. The │ ║ ║ ╟─────────────────╢ │█
- sum of the │ ║ ║ ║ Shared Data ║ │█
- mapped text, │ ║ ║ ╟─────────────────║6400000H │█
- data, and │ ║ ║ ║ Available ║ │█
- stack cannot │ ║ Unused ║ ║ for heap ║ │█
- exceed the │ ║ ║ ║ expansion ║ │█
- installation │ ║ ║ ╟─────────────────║ │█
- dependent limit │ ║ ║ ║ Heap ║ │█
- (typically the │ ║ ║ ╟─────────────────╢ │█
- sum of installed │ ║ ║ ║ Initialized Data║ │█
- RAM plus the │ ║ ║ ╟─────────────────╢ │█
- size of │ ╟─────────────────╢ ║ Stack ║ │█
- the paging │ ║ ║ ╟─────────────────╢1880000H │█
- area on │ ║ Text ║ ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║ │█
- the disk). │ ║ ║ ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║ │█
- │ ╚═════════════════╝0 ╚═════════════════╝0 │█
- └─────────────────────────────────────────────────────┘█
- ██████████████████████████████████████████████████████
-
-
- Figure 7: IObound.c
-
- /*
- * IObound.c
- */
-
- #define IMAX 100
- #define JMAX 100
-
- #define BFS 2
- char buffer[BFS];
-
- main()
- {
- int i, j;
- int fd;
-
- /* Create a Disk File */
- fd = creat("scratch", 0600);
-
- for(i=0; i<IMAX; i++){
- sync();
- for(j=0; j<JMAX; j++){
- write(fd, buffer, BFS);
- }
- }
-
-
- sync();
- /* Return to Beginning of the File */
- lseek(fd, 0, 0);
-
- for(i=0; i<IMAX; i++){
- sync();
- for(j=0; j<JMAX; j++){
- read(fd, buffer, BFS);
- }
- }
-
- /* Remove the File */
- unlink('scratch');
- exit(0);
- }
-
-
- Figure 6: Cpubound.c
-
- /*
- * Cpubound.c
- */
- #define IMAX 100
- #define JMAX 1000
-
- int id[IMAX];
- int jd[JMAX];
-
- main()
- {
- int i, j;
-
- for(i=0; i<IMAX; i++){
- id[i] = i;
- for(j=0; j<JMAX; j++){
- jd[i] = j; calli(id, jd, j);
- }
- }
- exit(0);
- }
- calli(i, j, c)
- int *i, *j;
- int c;
- {
-
- int t;
- int ti = i[c];
- int tj = j[c];
-
- while(ti- -)
- t += (*(i++))+(*(j++))+(tj-);
-
- return(t);
- }
-
-
- Figure 8: Performance Table
-
- CPU Bound Performance (normalized)
-
- Real time User time System time
-
- Small-Model 286 32.4 (0.59) 32.2 0.0
- Middle-Model 286 40.6 (0.47) 40.5 0.0
- Large-Model 286 57.5 (0.33) 57.4 0.0
- Small-Model 386 19.0 (1.00) 18.9 0.0
-
- I/O Bound Performace (normalized)
-
- Real time User time System time
-
- Small-Model 286 38.5 (1.00) 0.3 13.3
- Middle-Model 286 38.6 (1.00) 0.7 12.8
- Large-Model 286 42.4 (0.91) 0.4 14.8
- Small-Model 386 38.4 (1.00) 0.2 12.4
-
-
- Figure 9: XENIX System Call Extensions to be avoided for portability.
-
- ╓┌──────────────┌────────────────────────────────────────────────────────────╖
- Entry Point Function
-
- chsize adjust file size
-
- creatsem semaphore operations
- nbwaitsem semaphore operations
- Entry Point Function
- nbwaitsem semaphore operations
- opensem semaphore operations
- sigsem semaphore operations
- waitsem semaphore operations
-
- execseg execute data
- unexecseg execute data
-
- ftime obsolete UNIX time function
-
- locking XENIX file locking
-
- nap sleep for a short time
-
- proctl process specific control function
-
- rdchk check for input without reading
-
- sdenter XENIX shared data extension
- sdfree XENIX shared data extension
- Entry Point Function
- sdfree XENIX shared data extension
- sdget XENIX shared data extension
- sdgetv XENIX shared data extension
- sdleave XENIX shared data extension
- sdwaitv XENIX shared data extension
-
- shutdn shutdown system
-
- swapon control paging devices
-
-
- ───────────────────────────────────────────────────────────────────────────
- Demand Paging and Virtual Memory
- ───────────────────────────────────────────────────────────────────────────
-
- Demand paging is a feature of the XENIX 386 operating system, built on top
- of the 386 chip architecture that allows a program to run even though all
- of its pages have not been loaded into memory. Instead, only those pages
- from the executable image stored on disk that are actually referenced by the
- program as it runs are loaded into memory.
-
- Whenever the program references a page that is not in memory, it causes a
- "page fault." The XENIX kernel acts in response by loading the requested
- page from the disk and restarting the faulting program. The effect of demand
- paging is to reduce the memory usage of a given program to those pages that
- it actually references during a particular invocation. This set of pages is
- called the "working set" and is usually smaller than the full size of the
- program, especially if that program is large.
-
- Demand paging occurs without any knowledge on the part of the application.
- For example, the second pass of the Microsoft 386 C compiler is
- approximately 300Kb; however its working set on a typical program is
- closer to 80Kb, depending on which Microsoft C language features are
- used.
-
- The effect of demand paging is to improve the throughput of the system on
- smaller memory configurations, and because it is not necessary to load
- programs into memory before they start execution, the latency of command
- execution can be greatly reduced.
-
- Virtual memory allows the real memory associated with a program's heap
- (allocated via malloc or sbrk calls) to be allocated on demand rather than
- at the time of the malloc or sbrk function call. A program can be assigned a
- large address space without incurring additional overhead for pages that
- would remain unused.
-
- When a program makes a memory allocation call, the kernel recognizes the
- change in the end of the virtual heap. However, it allocates no memory
- between the end of the old heap and the new location. It is only when a
- program accesses pages in the new heap region and causes a page fault that
- the kernel allocates empty pages of real memory. This feature is also
- called "zero-fill on demand."
-
- ████████████████████████████████████████████████████████████████████████████
-
- HEXCALC: An Instructive Pop-Up Calculator for Microsoft Windows
-
- Charles Petzold
-
- When you design a dialog box for either a Windows program or an OS/2
- Presentation Manager program, you have available a whole array of "control
- windows" that take the form of push buttons, check boxes, scroll bars, list
- boxes, edit fields, and text items. Simply list these controls in a dialog
- box template and they magically appear in the dialog box window when the
- dialog box is invoked.
-
- A little further into Windows programming, you realize that these control
- windows can also be plastered right onto the surface of your window's client
- area. All it takes is a call to CreateWindow specifying the window class and
- the style of the control. A demonstration of this technique was shown in my
- COLORSCR program. (See "A Simple Windows Application for Custom Color
- Mixing," MSJ, Vol. 2, No. 2.)
-
- Then you reach a frustrating impasse. What you really want to do is create a
- simple dialog box template that describes the size and position of the
- various controls, and use that template to put the controls on your window's
- client area. At first there doesn't appear to be any way to do this. The
- only Windows functions that use the dialog template are CreateDialog and
- DialogBox. But you want to have these controls on your main window, not on a
- dialog box.
-
- HEXCALC (see Figure 1) shows how this is done. HEXCALC is a pop-up window
- with 29 child window push-button controls. It does not contain even one
- CreateWindow call. Instead, it uses CreateDialog to create the program's
- main window. This window and all the child window push-button controls are
- defined in a dialog box template in HEXCALC's resource script. This article
- will take a close look at HEXCALC, then compares HEXCALCW, a version for
- Windows 2.0, with HEXCALCP, which runs under Presentation Manager in the
- OS/2 systems.
-
-
- Using HEXCALC
-
- HEXCALC is a ten-function infix-notation hexadecimal calculator with a
- full keyboard and mouse interface. It works with 32-bit unsigned long
- integers and does addition, subtraction, multiplication, division,
- remainders, bitwise AND, OR, and Exclusive-OR, as well as left and right
- bit shifts. The buttons for the operations use C notation, except for the
- bit shift buttons, which display one angle bracket rather than two.
-
- To do a calculation, type or click in the first number (up to eight
- hexadecimal digits) followed by the operation and then the second number.
- You can show the result by clicking the Equals button or by pressing either
- the keyboard Equals key or Enter key. To correct your entries, you can use
- either the BackSpace key, the left cursor movement key, or the Back button.
- Pressing Escape or clicking the result box clears the current entry.
-
- Results are always truncated to 32 bits, just as if you were performing the
- operation on two unsigned long integers in a C program. The only special
- handling is a check for division by zero before doing division or a
- remainder operation. In this case, HEXCALC sets the result to FFFFFFFF.
-
-
- Inspired by PIFEDIT
-
- I don't know how many Windows programmers have taken a close look at
- PIFEDIT, but it does some very interesting things. PIFEDIT's main window
- looks a lot like a full-screen dialog box, and in fact it is.
-
- All those static text fields, edit fields, check boxes, and radio buttons on
- the surface of PIFEDIT's main window are defined in a dialog box template.
- Like HEXCALC, PIFEDIT doesn't make any CreateWindow calls. PIFEDIT's main
- window is created instead with a call to CreateDialog. The messages to this
- window are processed by a window function in the PIFEDIT program.
-
- The technique demonstrated in PIFEDIT and HEXCALC can be used for any
- program that would otherwise call CreateWindow to create child window
- controls on the surface of its main window. These controls can include
- buttons, scroll bars, static text fields, edit fields, list boxes, and even
- controls that you design yourself. The technique works best when these
- child window controls are of a fixed size and position relative to the
- size of a system font character, because that's how they are defined in the
- dialog box template.
-
- This is not the technique that the CALC.EXE program uses, however. CALC
- draws its own buttons rather than using child window button controls. This
- results in a faster display, but CALC is then forced to do hit testing on
- the mouse-click messages. Because push-button controls do their own hit
- testing, HEXCALC can ignore all mouse messages.
-
-
- The HEXCALC Program
-
- The various parts of the Windows 2.0 version of HEXCALC are shown in
- Figures 2W-6W.
-
- With an installed version of the Microsoft C Compiler Version 4.0 or 5.0
- and the Windows Software Development Kit Version 2.0, you can create
- HEXCALCW.EXE by running
-
- MAKE HEXCALCW
-
- Let's begin by taking a look at the HEXCALCW.RC resource script. This
- contains the dialog box template that defines HEXCALC's main window and all
- the push-button controls.
-
-
- Resource Script
-
- The dialog box template shown in the HEXCALCW.RC resource script describes
- the size and appearance of HEXCALC's window. The style bits are
- WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, and WS_MINIMIZEBOX. Except for
- WS_MINIMIZEBOX, these are normal for a modeless dialog box. The text
- appearing in the caption bar is "Hex Calculator".
-
- As is the case with all dialog box templates, the values in the DIALOG
- statement that specify the window's initial position and size are in units
- of 1/4 the width and 1/8 the height of a system font character. It is 102
- units (or 25 1/2 characters) wide and 122 units (or 15 1/4 characters)
- high.
-
- The 29 buttons displayed in the calculator are defined with PUSHBUTTON
- statements. The string in double quotes is the text that appears inside the
- button. The number that follows is the control ID. This is followed by the
- horizontal and vertical position of the control relative to the upper-left-
- hand corner of the dialog box's client area. The final two numbers indicate
- the horizontal and vertical size of the control. All the push buttons are
- a standard height: 14 units, or 1 3/4 characters.
-
-
- Control IDs
-
- The control ID is an integer that the child window uses to identify itself
- to its parent. The control ID also allows the parent window to send messages
- to the child window control (using SendDlgItemMessage) without knowing the
- window handle of the control. A parent can obtain the window handle of a
- control from its ID using GetDlgItem.
-
- Normally Windows programmers use identifiers defined in a header file for
- these control IDs. The header file is then included in both the program and
- the resource script using a #include statement.
-
- The control IDs I've chosen for these push buttons may appear to be random,
- but there really is a method to the madness. The control IDs have been set
- to the ASCII codes of the corresponding number, letter, or symbol that
- appears inside the push button.
-
- As will be seen in the HEXCALC.C listing, this is both a cheap and easy
- method to add a keyboard interface to the calculator. Clicking on a child
- window button with the mouse, the child window sends to its parent window
- (which is HEXCALC's main window) a WM_COMMAND message with wParam equal to
- the control ID. When the user presses a character key on the keyboard,
- HEXCALC's main window receives a WM_CHAR message with wParam equal to the
- ASCII code of the character. This means that the same logic can be used for
- both key presses and mouse clicks.
-
-
- The CLASS Statement
-
- The big difference between HEXCALC's dialog box template and most other
- dialog box templates is the presence of the CLASS statement. You can read in
- the Windows Programmer's Utility Guide, that (I quote it in its entirety)
- "This optional statement defines the class of the dialog box" and the
- parameter to CLASS "is an integer or a string (enclosed in double quotes)
- that identifies the dialog box class." At this point, perhaps some
- additional explanation is required.
-
- All windows created in Microsoft(R) Windows are based on a particular window
- class. Among other things, this class specifies a function (called the
- window function) that processes messages to the window. Normally in a
- Windows program you first register a window class and then create a window
- based on that class by calling CreateWindow.
-
- When you create a dialog box by calling CreateDialog or DialogBox, Windows
- normally uses a default window class that Windows itself defines. The
- function specified in this window class is internal to Windows. It is this
- window function that processes the messages to the dialog box. This
- function will pass many of the messages to a "dialog function" located in
- your program. The address of this dialog function is specified as a
- parameter to the CreateDialog or DialogBox function. That's the normal case.
- However, what we're doing in HEXCALC is somewhat different.
-
- By specifying a window class in the dialog box template, you're telling
- Windows not to use its own window class for dialog boxes created using this
- template. You're telling Windows to use a different window class,
- specifically the one indicated in the CLASS statement. This provides
- Windows with the address of a window function. It is this function that
- will receive messages for the dialog box. The window function, called
- WndProc, is part of HEXCALC.
-
- For an ordinary dialog box you wouldn't want to do this, because then you
- would be faced with the problem of duplicating all the logic required for
- changing the input focus among child window controls when the user presses
- the Tab key or cursor movement keys. But for a program like HEXCALC, this
- technique is ideal.
-
-
- Creating the Window
-
- Like most Windows programs, HEXCALC begins by registering a window class
- (HexCalcW) that among other things specifies the window function (WndProc)
- that will process messages to windows based on that class. But then, rather
- than call CreateWindow, HEXCALC calls CreateDialog, a function that usually
- creates a modeless dialog box.
-
- The second parameter to the CreateDialog function is the name of the dialog
- box template, which is also named HexCalcW. The third parameter to
- CreateDialog is normally the window handle of the dialog box's parent.
- Because you're using this dialog box as the main window in HEXCALC, this is
- set to 0.
-
- The fourth parameter to CreateDialog is normally a far pointer to the
- program's dialog function for the dialog box. This is the function to which
- some (but not all) messages processed by the actual dialog box window
- function are passed. Usually that window function is internal to Windows,
- but not when a class for the dialog box is specified in the template.
- Because the WndProc window function itself is part of HEXCALC, this dialog
- function is not needed. The fourth parameter is set to NULL.
-
- Now it's time to ask: What does Windows do during the CreateDialog call?
- Consider that Windows has access to the instance handle of the program and
- the name of the dialog box template. It gets this from the CreateDialog
- call:
-
- hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
-
- The szAppName variable is a character array that contains the name HexCalcW,
- the name of the dialog box template. This means that Windows can load the
- dialog box template into memory. The template (in its ASCII form) begins
- like this:
-
- HexCalcW DIALOG 32768, 0, 102, 122
- STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZE
- CLASS "HexCalcW"
- CAPTION "Hex Calculator"
-
- Using the parameters to the CreateDialog function along with the top of the
- dialog box template, Windows is able to call CreateWindow with the
- following parameters:
-
- hWnd = CreateWindow ("HexCalcW", "Hex Calculator",
- WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZE,
- 32768,0, 102 * 4 / xChar, 122 * 8 / yChar, NULL, NULL, hInstance, NULL) ;
-
- This creates HEXCALC's top-level window. Assigning a value of 32768 to the
- initial x position specifies that Windows use the default position (when
- using a CreateWindow call in a program, CW_USEDEFAULT is the identifier).
- The xChar and yChar values shown here would be determined by Windows. They
- are the width and height of a system font character and are used to
- translate the dialog box coordinate and size units into pixels. The hWnd
- parameter returned from this call is eventually passed back to the program
- as the return value from CreateDialog.
-
- Still processing the CreateDialog call, Windows will then create child
- windows based on the PUSHBUTTON statements in the dialog box template. This
- is the first PUSHBUTTON statement:
-
- PUSHBUTTON "D", 68, 8, 24, 14, 14
-
- This becomes another CreateWindow call:
-
- hCtl = CreateWindow ("button", "D", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON |
- 8 * 4 / xChar, 24 * 8 / yChar, 14 * 4 / xChar,
- 14 * 8 / yChar, hWnd, 68, hInstance, NULL) ;
-
- Windows will call CreateWindow 29 times for each of the push buttons. You
- should note that the eighth parameter (the parent window) is set to hWnd.
- This will permit the child window controls to send WM_COMMAND messages to
- their parent window. The ninth parameter will be set to the control ID.
-
- Because the dialog box template specifies a window class, Windows does
- almost nothing else during the CreateDialog function except to call
- CreateWindow multiple times based on the template. When CreateDialog
- returns, all the windows have been created. The program proceeds normally
- just as if it had done all that work itself. HEXCALC has no other odd code.
- It has a normal message loop and a normal window function.
-
- So, in effect, HEXCALC is not very different from my earlier COLORSCR
- program. Both programs create a main window and several child window
- controls. The difference is that COLORSCR does it with explicit CreateWindow
- calls. HEXCALC simply gives Windows a dialog box template and says, "Here,
- you do this stuff. Tell me when you're finished."
-
-
- Window Function
-
- I'm not going to explain the actual calculator logic in HEXCALC because
- that's really not the point of this exercise. However, let's take a look at
- some of the code near the top of WndProc.
-
- Because the control IDs of the push buttons are set to the ASCII code of the
- push-button text, the keyboard interface is very simple. On a WM_CHAR
- message, the wParam parameter (the ASCII code of the key) is first converted
- to upper case and the Enter key is translated to an equals sign.
-
- HEXCALC determines if the key corresponds to a button by calling GetDlgItem.
- This function translates a control ID of a child window into the child
- window handle:
-
- hButton = GetDlgItem (hWnd, wParam)
-
- If hButton is 0, then the key does not correspond to a button, and HEXCALC
- beeps. Otherwise, HEXCALC flashes the button by sending it a couple of
- BM_SETSTATE messages:
-
- SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
- SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
-
- This adds a nice touch to the keyboard interface of HEXCALC and-as you can
- see-it's almost trivial.
-
- When WndProc processes WM_COMMAND messages, it always sets the input focus
- to the parent window:
-
- case WM_COMMAND: SetFocus (hWnd) ;
-
- Otherwise, the input focus would be shifted to one of the buttons whenever
- it was clicked with the mouse. This would alter the appearance of the button
- and also prevent WndProc from seeing subsequent WM_CHAR messages.
-
-
- The Presentation Manager Version
-
- The conversion of HEXCALC to the OS/2 Presentation Manager is relatively
- straightforward. The result──called HEXCALCP.EXE──is shown in Figures
- 2PM-6PM.
-
- From a distance of about six feet, a Presentation Manager program looks the
- same as a Windows program. Structurally, the application program interfaces
- in the two environments are quite similar. However, on closer
- examination, lots of little changes are required. Some of these changes
- are described by Michael Geary in "Converting Windows Applications for
- Microsoft(R) OS/2 Presentation Manager," in this issue.
-
- The format of the dialog box template in HEXCALCP.RC is slightly different
- because it reflects the structure of "standard windows" in the
- Presentation Manager. The main window is a "frame" window, the client
- window is a child of the frame, and the push buttons are children of the
- client window. This parent-child relationship is described by the window
- definitions within nested brackets in the template. Messages to the client
- window in HEXCALCP are handled by the ClientWndProc window function.
-
- You will remember that HEXCALC must make a call to SetFocus whenever a
- button is clicked to prevent the button from keeping the keyboard input
- focus. Using the Presentation Manager, buttons can have a BS_NOMOUSEFOCUS
- style, which means that they will not receive the input focus when clicked
- with the mouse. The SetFocus call is made unnecessary by specifying the
- BS_NOMOUSEFOCUS style for the push buttons in the HEXCALCP.RC resource
- script.
-
- The keyboard interface in HEXCALCP is somewhat cleaner in the Presentation
- Manager version. First, all keyboard messages are WM_CHAR messages so
- HEXCALCP doesn't have to process WM_KEYDOWN messages. Secondly, if the key
- pressed corresponds to one of the buttons, then HEXCALCP sends the button a
- single BM_CLICK message:
-
- WinSendMsg (hWndButton, BM_CLICK, 0L, 0L) ;
-
- This message not only causes the button to flash, but also causes the button
- to send its owner a WM_COMMAND message. In the Windows version, the
- keyboard message falls through to be processed within the WM_COMMAND case.
-
- Termination works a little differently in the two programs. In Windows, the
- Close option on the system menu generates a WM_CLOSE message. If the message
- is passed on to DefWindowProc, then Windows destroys the window by calling
- DestroyWindow and the window function receives a WM_DESTROY message. The
- window function responds by posting a WM_QUIT message to itself with
- PostQuitMessage. This message causes the GetMessage function in WinMain to
- return 0, and the program terminates.
-
- In the Presentation Manager, the WM_CLOSE message is still generated by the
- system menu. If the program passes this message to WinDefWindowProc, then
- the Presentation Manager posts a WM_QUIT message to the message queue. As in
- Windows, this message causes the WinGetMsg call in main to return 0.
- However, the window still exists, so the program must explicitly destroy the
- window in main using a call to WinDestroyWindow. This function causes the
- Presentation Manager to destroy the frame window as well as all the
- children and other descendants of the frame.
-
-
- Figure 2W: HEXCALCW Make File
-
- hexcalcw.obj: hexcalcw.c
- cl -c -D LINT_ARGS -Gsw -Os -W2 -Zp hexcalcw.c
-
- hexcalcw.res : hexcalcw.rc hexcalcw.ico
- rc -r hexcalcw.rc
-
- hexcalcw.exe: hexcalcw.obj hexcalcw.def hexcalcw.res
- link4 hexcalcw, /align:16, /map, slibw, hexcalcw
- rc hexcalcw.res
-
-
- Figure 2PM: HEXCALCP Make File
-
- hexcalcp.obj: hexcalcp.c hexcalcp.h
- cl -c -D LINT_ARGS -G2sw -W2 -Zp hexcalcp.c
-
- hexcalcp.res : hexcalcp.rc hexcalcp.h
- rc -r hexcalcp.rc
-
- hexcalcp.exe: hexcalcp.obj hexcalcp.def hexcalcp.res
- link hexcalcp, /align:16, /map, slibc5 os2, hexcalcp
- rc hexcalcp.res
-
-
- Figure 3W: HEXCALCW.RC Resource Script
-
- #include <windows.h>
-
- HexCalcW ICON hexcalcw.ico
-
- HexCalcW DIALOG 32768, 0, 102, 122
- STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
- CLASS "HexCalcW"
- CAPTION "Hex Calculator"
- BEGIN
- PUSHBUTTON "D", 68, 8, 24, 14, 14
- PUSHBUTTON "A", 65, 8, 40, 14, 14
- PUSHBUTTON "7", 55, 8, 56, 14, 14
- PUSHBUTTON "4", 52, 8, 72, 14, 14
- PUSHBUTTON "1", 49, 8, 88, 14, 14
- PUSHBUTTON "0", 48, 8, 104, 14, 14
- PUSHBUTTON "0", 27, 26, 4, 50, 14
- PUSHBUTTON "E", 69, 26, 24, 14, 14
- PUSHBUTTON "B", 66, 26, 40, 14, 14
- PUSHBUTTON "8", 56, 26, 56, 14, 14
- PUSHBUTTON "5", 53, 26, 72, 14, 14
- PUSHBUTTON "2", 50, 26, 88, 14, 14
- PUSHBUTTON "Back", 8, 26, 104, 32, 14
- PUSHBUTTON "C", 67, 44, 40, 14, 14
- PUSHBUTTON "F", 70, 44, 24, 14, 14
- PUSHBUTTON "9", 57, 44, 56, 14, 14
- PUSHBUTTON "6", 54, 44, 72, 14, 14
- PUSHBUTTON "3", 51, 44, 88, 14, 14
- PUSHBUTTON "+", 43, 62, 24, 14, 14
- PUSHBUTTON "-", 45, 62, 40, 14, 14
- PUSHBUTTON "*", 42, 62, 56, 14, 14
- PUSHBUTTON "/", 47, 62, 72, 14, 14
- PUSHBUTTON "%", 37, 62, 88, 14, 14
- PUSHBUTTON "Equals", 61, 62, 104, 32, 14
- PUSHBUTTON "&&", 38, 80, 24, 14, 14
- PUSHBUTTON "|", 124, 80, 40, 14, 14
- PUSHBUTTON "^", 94, 80, 56, 14, 14
- PUSHBUTTON "<", 60, 80, 72, 14, 14
- PUSHBUTTON ">", 62, 80, 88, 14, 14
- END
-
-
- Figure 3PM: HEXCALCP.RC Resource Script
-
- #include <os2.h>
- #include "hexcalcp.h"
-
- WINDOWTEMPLATE ID_HEXCALC
- {
- FRAME "Hex Calculator", 1, 110, 40, 102, 122, WS_VISIBLE |
- FS_TITLEBAR | FS_SYSMENU | FS_MINBUTTON | FS_BORDER
- {
- WINDOW "", FID_CLIENT, 0, 0, 102, 122, "HexCalcP", WS_VISIBLE
- {
- PUSHBUTTON "D", 68, 8, 88, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "A", 65, 8, 72, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "7", 55, 8, 56, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "4", 52, 8, 40, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "1", 49, 8, 24, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "0", 48, 8, 4, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "0", 27, 26, 104, 50, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "E", 69, 26, 88, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "B", 66, 26, 72, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "8", 56, 26, 56, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "5", 53, 26, 40, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "2", 50, 26, 24, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "Back", 8, 26, 4, 32, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "F", 70, 44, 88, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "C", 67, 44, 72, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "9", 57, 44, 56, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "6", 54, 44, 40, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "3", 51, 44, 24, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "+", 43, 62, 88, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "-", 45, 62, 72, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "*", 42, 62, 56, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "/", 47, 62, 40, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "%", 37, 62, 24, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "Equals", 61, 62, 4, 32, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "&", 38, 80, 88, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "|", 124, 80, 72, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "^", 94, 80, 56, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON "<", 60, 80, 40, 14, 14, BS_NOMOUSEFOCUS
- PUSHBUTTON ">", 62, 80, 24, 14, 14, BS_NOMOUSEFOCUS
- }
- }
- }
-
-
- Figure 4W: HEXCALCW.DEF Module Definition File
-
- NAME HexCalcW
- DESCRIPTION 'Hexadecimal Calculator(C) Charles Petzold 1987'
- STUB 'WINSTUB.EXE'
- CODE MOVEABLE
- DATA MOVEABLE MULTIPLE
- HEAPSIZE 1024
- STACKSIZE 4096
- EXPORTS WndProc
-
-
- Figure 4PM: HEXCALCP.DEF Module Definition File
-
- NAME HEXCALCP
- DESCRIPTION 'Hexadecimal Calculator(C) Charles Petzold
- 1987'
- STUB 'OS2STUB.EXE'
- HEAPSIZE 1024
- STACKSIZE 4096
- EXPORTS ClientWndProc
-
-
- Figure 5W:
-
- There is no special header file equivalent
- for the Windows version.
-
-
- Figure 5PM: HEXCALCP.H Header File
-
- #define ID_HEXCALC 1
-
-
- Figure 6W: HEXCALCW.C is the Source Code Listing
-
- #include <windows.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
-
- long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;
-
- int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
- HANDLE hInstance, hPrevInstance;
- LPSTR lpszCmdLine;
- int nCmdShow;
- {
- static char szAppName [] = "HexCalcW" ;
- HWND hWnd ;
- MSG msg;
- WNDCLASS wndclass ;
-
- if (!hPrevInstance)
- {
- wndclass.style = CS_HREDRAW | CS_VREDRAW;
- wndclass.lpfnWndProc = WndProc ;
- wndclass.cbClsExtra = 0 ;
- wndclass.cbWndExtra = 0 ;
- wndclass.hInstance = hInstance ;
- wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
- wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
- wndclass.hbrBackground = COLOR_WINDOW + 1 ;
- wndclass.lpszMenuName = NULL ;
- wndclass.lpszClassName = szAppName ;
-
- if (!RegisterClass (&wndclass))
- return FALSE ;
- }
-
- hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
-
- ShowWindow (hWnd, nCmdShow) ;
-
- while (GetMessage (&msg, NULL, 0, 0))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- return msg.wParam ;
- }
-
- void ShowNumber (hWnd, dwNumber)
- HWND hWnd ;
- DWORD dwNumber ;
- {
- char szBuffer [20] ;
-
- SetDlgItemText (hWnd, VK_ESCAPE,
- strupr (ltoa (dwNumber, szBuffer, 16))) ;
- }
-
- DWORD CalcIt (dwFirstNum, nOperation, dwNum)
- DWORD dwFirstNum, dwNum ;
- short nOperation ;
- {
- switch (nOperation)
- {
- case '=' : return dwNum ;
- case '+' : return dwFirstNum + dwNum ;
- case '-' : return dwFirstNum - dwNum ;
- case '*' : return dwFirstNum * dwNum ;
- case '&' : return dwFirstNum & dwNum ;
- case '|' : return dwFirstNum | dwNum ;
- case '^' : return dwFirstNum ^ dwNum ;
- case '<' : return dwFirstNum << dwNum ;
- case '>' : return dwFirstNum >> dwNum ;
- case '/' : return dwNum ? dwFirstNum / dwNum : ULONG_MAX ;
- case '%' : return dwNum ? dwFirstNum % dwNum : ULONG_MAX ;
- default : return 0L ;
- }
- }
-
- long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
- HWND hWnd;
- unsigned iMessage;
- WORD wParam;
- LONG lParam;
- {
- static BOOL bNewNumber = TRUE ;
- static DWORD dwNumber, dwFirstNum ;
- static short nOperation = '=' ;
- HWND hButton ;
-
- switch (iMessage)
- {
- case WM_KEYDOWN: /* left arrow -> backspace */
- if (wParam != VK_LEFT)
- break ;
- wParam = VK_BACK ;
- /* fall through */
- case WM_CHAR:
- if ((wParam = toupper (wParam)) == VK_RETURN)
- wParam = '=' ;
-
- if (hButton = GetDlgItem (hWnd, wParam))
- {
- SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
- SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
- }
- else
- {
- MessageBeep (0) ;
- break ;
- }
- /* fall through */
- case WM_COMMAND:
- SetFocus (hWnd) ;
-
- if (wParam == VK_BACK) /* backspace */
- ShowNumber (hWnd, dwNumber /= 16) ;
-
- else if (wParam == VK_ESCAPE) /* escape */
- ShowNumber (hWnd, dwNumber = 0L) ;
-
- else if (isxdigit (wParam)) /* hex digit */
- {
- if (bNewNumber)
- {
- dwFirstNum = dwNumber ;
- dwNumber = 0L ;
- }
- bNewNumber = FALSE ;
-
- if (dwNumber <= ULONG_MAX >> 4)
- ShowNumber (hWnd, dwNumber =
- 16 * dwNumber + wParam -
- (isdigit (wParam) ? '0' : 'A' - 10)) ;
- else
- MessageBeep (0) ;
- }
-
- else /* operation */
- {
- if (!bNewNumber)
- ShowNumber (hWnd, dwNumber =
- CalcIt (dwFirstNum, nOperation,
- dwNumber)) ;
- bNewNumber = TRUE ;
- nOperation = wParam ;
- }
- break ;
-
- case WM_DESTROY:
- PostQuitMessage (0) ;
- break ;
-
- default :
- return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
- }
- return 0L ;
- }
- #include <windows.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
-
- long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;
-
- int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
- HANDLE hInstance, hPrevInstance;
- LPSTR lpszCmdLine;
- int nCmdShow;
- {
- static char szAppName [] = "HexCalcW" ;
- HWND hWnd ;
- MSG msg;
- WNDCLASS wndclass ;
-
- if (!hPrevInstance)
- {
- wndclass.style = CS_HREDRAW | CS_VREDRAW;
- wndclass.lpfnWndProc = WndProc ;
- wndclass.cbClsExtra = 0 ;
- wndclass.cbWndExtra = 0 ;
- wndclass.hInstance = hInstance ;
- wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
- wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
- wndclass.hbrBackground = COLOR_WINDOW + 1 ;
- wndclass.lpszMenuName = NULL ;
- wndclass.lpszClassName = szAppName ;
-
- if (!RegisterClass (&wndclass))
- return FALSE ;
- }
-
- hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
-
- ShowWindow (hWnd, nCmdShow) ;
-
- while (GetMessage (&msg, NULL, 0, 0))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- return msg.wParam ;
- }
-
- void ShowNumber (hWnd, dwNumber)
- HWND hWnd ;
- DWORD dwNumber ;
- {
- char szBuffer [20] ;
-
- SetDlgItemText (hWnd, VK_ESCAPE,
- strupr (ltoa (dwNumber, szBuffer, 16))) ;
- }
-
- DWORD CalcIt (dwFirstNum, nOperation, dwNum)
- DWORD dwFirstNum, dwNum ;
- short nOperation ;
- {
- switch (nOperation)
- {
- case '=' : return dwNum ;
- case '+' : return dwFirstNum + dwNum ;
- case '-' : return dwFirstNum - dwNum ;
- case '*' : return dwFirstNum * dwNum ;
- case '&' : return dwFirstNum & dwNum ;
- case '|' : return dwFirstNum | dwNum ;
- case '^' : return dwFirstNum ^ dwNum ;
- case '<' : return dwFirstNum << dwNum ;
- case '>' : return dwFirstNum >> dwNum ;
- case '/' : return dwNum ? dwFirstNum / dwNum : ULONG_MAX ;
- case '%' : return dwNum ? dwFirstNum % dwNum : ULONG_MAX ;
- default : return 0L ;
- }
- }
-
- long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
- HWND hWnd;
- unsigned iMessage;
- WORD wParam;
- LONG lParam;
- {
- static BOOL bNewNumber = TRUE ;
- static DWORD dwNumber, dwFirstNum ;
- static short nOperation = '=' ;
- HWND hButton ;
-
- switch (iMessage)
- {
- case WM_KEYDOWN: /* left arrow -> backspace */
- if (wParam != VK_LEFT)
- break ;
- wParam = VK_BACK ;
- /* fall through */
- case WM_CHAR:
- if ((wParam = toupper (wParam)) == VK_RETURN)
- wParam = '=' ;
-
- if (hButton = GetDlgItem (hWnd, wParam))
- {
- SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
- SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
- }
- else
- {
- MessageBeep (0) ;
- break ;
- }
- /* fall through */
- case WM_COMMAND:
- SetFocus (hWnd) ;
-
- if (wParam == VK_BACK) /* backspace */
- ShowNumber (hWnd, dwNumber /= 16) ;
-
- else if (wParam == VK_ESCAPE) /* escape */
- ShowNumber (hWnd, dwNumber = 0L) ;
-
- else if (isxdigit (wParam)) /* hex digit */
- {
- if (bNewNumber)
- {
- dwFirstNum = dwNumber ;
- dwNumber = 0L ;
- }
- bNewNumber = FALSE ;
-
- if (dwNumber <= ULONG_MAX >> 4)
- ShowNumber (hWnd, dwNumber =
- 16 * dwNumber + wParam -
- (isdigit (wParam) ? '0' : 'A' - 10)) ;
- else
- MessageBeep (0) ;
- }
-
- else /* operation */
- {
- if (!bNewNumber)
- ShowNumber (hWnd, dwNumber =
- CalcIt (dwFirstNum, nOperation,
- dwNumber)) ;
- bNewNumber = TRUE ;
- nOperation = wParam ;
- }
- break ;
-
- case WM_DESTROY:
- PostQuitMessage (0) ;
- break ;
-
- default :
- return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
- }
- return 0L ;
- }
-
-
- Figure 6PM: HEXCALCP.C is the Source Code Listing
-
- #include <os2.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include "hexcalcp.h"
-
- ULONG EXPENTRY ClientWndProc (HWND, USHORT, ULONG, ULONG) ;
-
- main ()
-
- {
- static CHAR szClassName [] = "HexCalcP" ;
- HAB hAB ;
- HMQ hMQ ;
- HWND hWnd ;
- QMSG qmsg ;
-
- hAB = WinInitialize () ;
- hMQ = WinCreateMsgQueue (hAB, 0) ;
-
- WinRegisterClass (hAB, szClassName, ClientWndProc, 0L, 0, NULL) ;
-
- hWnd = WinLoadDlg (HWND_DESKTOP, HWND_DESKTOP, NULL, NULL,
- ID_HEXCALC, NULL) ;
-
- WinSetFocus (HWND_DESKTOP, WinWindowFromID (hWnd, FID_CLIENT)) ;
- while (WinGetMsg (hAB, &qmsg, NULL, 0, 0)) WinDispatchMsg (hAB,&qmsg);
-
- WinDestroyWindow (hWnd) ;
- WinDestroyMsgQueue (hMQ) ;
- WinTerminate (hAB) ;
-
- return 0 ;
- }
-
- void ShowNumber (hWnd, lNumber)
- HWND hWnd ;
- ULONG lNumber ;
- {
- CHAR szBuffer [20] ;
- HWND hWndResult ;
-
- hWndResult = WinWindowFromID (hWnd, VK_ESCAPE) ;
-
- WinSetWindowText (hWndResult, strupr (ltoa (lNumber, szBuffer, 16))) ;
- }
-
- ULONG CalcIt (lFirstNum, iOperation, lNum)
- ULONG lFirstNum, lNum ;
- SHORT iOperation ;
- {
- switch (iOperation)
- {
- case '=' : return lNum ;
- case '+' : return lFirstNum + lNum ;
- case '-' : return lFirstNum - lNum ;
- case '*' : return lFirstNum * lNum ;
- case '&' : return lFirstNum & lNum ;
- case '|' : return lFirstNum | lNum ;
- case '^' : return lFirstNum ^ lNum ;
- case '<' : return lFirstNum << lNum ;
- case '>' : return lFirstNum >> lNum ;
- case '/' : return lNum ? lFirstNum / lNum :
- ULONG_MAX ;
- case '%' : return lNum ? lFirstNum % lNum :
- ULONG_MAX ;
- default : return 0L ;
- }
- }
-
- ULONG EXPENTRY ClientWndProc (hWnd, nMessage, lParam1, lParam2)
- HWND hWnd ;
- USHORT nMessage ;
- ULONG lParam1 ;
- ULONG lParam2 ;
- {
- static BOOL bNewNumber = TRUE ;
- static ULONG lNumber, lFirstNum ;
- static SHORT iOperation = '=' ;
- HWND hWndButton ;
- SHORT idButton ;
-
- switch (nMessage)
- {
- case WM_CHAR:
- if (lParam1 & KC_KEYUP)
- break ;
-
- if (HIUSHORT (lParam2) == VK_LEFT) /* left arrow to */
- LOUSHORT (lParam2) = VK_BACK ; /* backspace */
- if (HIUSHORT (lParam2) == VK_RETURN) /* return to */
- LOUSHORT (lParam2) = '=' ; /* equals */
- if (LOUSHORT (lParam2) == 0)
- break ;
-
- LOUSHORT (lParam2) = toupper (LOUSHORT (lParam2)) ;
- if (hWndButton = WinWindowFromID (hWnd, LOUSHORT (lParam2)))
- WinSendMsg (hWndButton, BM_CLICK, 0L, 0L) ;
- else
- WinAlarm (HWND_DESKTOP, WA_ERROR) ;
- return 1L ;
-
- case WM_COMMAND:
- idButton = LOUSHORT (lParam1) ;
- if (idButton == VK_BACK) /* backspace */
- ShowNumber (hWnd, lNumber /= 16) ;
- else if (idButton == VK_ESCAPE) /* escape */
- ShowNumber (hWnd, lNumber = 0L) ;
- else if (isxdigit (idButton)) /* hex digit */
- {
- if (bNewNumber)
- {
- lFirstNum = lNumber ;
- lNumber = 0L ;
- }
- bNewNumber = FALSE ;
- if (lNumber <= ULONG_MAX >> 4)
- ShowNumber (hWnd, lNumber =
- 16 * lNumber + idButton -
- (isdigit (idButton) ? '0' : 'A' - 10)) ;
- else
- WinAlarm (HWND_DESKTOP, WA_ERROR) ;
- }
-
- else /* operation */
- {
- if (!bNewNumber)
- ShowNumber (hWnd, lNumber =
- CalcIt (lFirstNum, iOperation, lNumber)) ;
- bNewNumber = TRUE ;
- iOperation = idButton ;
- }
- break ;
- default :
- return WinDefWindowProc (hWnd, nMessage, lParam1, lParam2) ;
- }
- return 0L ;
-
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- Effectively Using Far and Huge Data Pointers In Your Microsoft C Programs
-
- Kaare Christian
-
- Program pointers are one of the most important features of the C language,
- because they are often the only way to express your thoughts clearly and
- precisely. Addresses are critical in all programming languages, but most
- languages try to take care of addressing details for you, thereby hiding
- crucial information about what happens when your program executes. Only
- the C language brings these details to the fore, by allowing you to express
- pointer operations in the text of the program.
-
- In the early, heady days of C, pointers were 16-bit quantities. Newcomers to
- C had to learn pointer arithmetic as well as C's somewhat difficult notation
- for using pointers, but they did not have to worry much about the underlying
- machine, the PDP-11. Pointer operations are beautifully supported by the
- simple and consistent architecture of the PDP-11, but only within a 64Kb
- data space.
-
- The PC family of computers is based on Intel(R) processors, which use a
- segmented architecture. If your data fits within one 64Kb space
- (Microsoft(R) C small- or medium-model programs), then pointer operations
- on the PC will work efficiently and consistently, just as on the
- venerable PDP-11. (This discussion applies to the segmentation model of
- the 8086, 8088, 186, 188 and the real mode on the 286 and 386. Protected
- mode segmentation is different and is not discussed.) However, if you do
- need the extra memory that the PC architecture offers, then you must learn
- to use far and huge pointers.
-
- Microsoft C lets you mix data types within a single program. You can create
- a far pointer in a small-model program or a near pointer in a huge-model
- program. My experience has been with occasional use of pointers to far or
- huge data in small-model programs, because the software that I work on tends
- to be small except for a few large data structures. Many of my comments will
- apply to all pointers in a compact-, large-, or huge-model program, because
- in those models data pointers are, by default, far or huge. All of the
- sample programs in this article were compiled with Microsoft C Version 4.0,
- using the small model.
-
- Besides pointers to far and huge data, Microsoft C also has far and huge
- data types. In Microsoft C there are two methods for creating a very large
- array: you can declare it as a huge or far array of integers or you can
- declare a pointer to a huge or far integer and then use halloc, the huge
- memory allocation subroutine, to allocate the space. The second approach
- is preferable because it is more flexible. Declaring a huge array forces you
- to decide in advance the exact array size; using halloc to create the array
- allows your program to adjust to the actual amount of memory present on the
- computer when it executes. This article specifically examines pointers to
- huge and far data, although some of the caveats and much of the discussion
- on efficiency also applies to huge or far arrays.
-
- As a prerequisite for grasping the following material, you must first
- understand how pointers are used in C and C casts. If you really know the
- basics of these two topics, then the program in Figure 1 should be a snap.
- Before reading the next paragraph, look at the program and predict its
- output.
-
- The answer: 100 and 200. The first printf statement prints the number of
- integers in the interval between where ip points and where i is located.
- Since ip was assigned the location 100 integers past where i is stored,
- naturally the answer is 100. In the second printf statement, ip and the
- location of i are cast to unsigned and then subtracted. The cast doesn't
- change the bit patterns, but it does change the operation from a pointer
- subtraction to an ordinary unsigned subtraction. The second answer is 200
- because the two bit patterns differ by that amount. If you ask, why 200,
- it's because 100 integers at 2 bytes each makes 200. The statement ip += 100
- actually added 200 to ip's binary value. If you passed the quiz, then read
- on. If not, then you need to learn more about ordinary pointer operations
- before you can understand the extraordinary pointer operations of
- Microsoft C far and huge pointers.
-
- A pointer to far data, usually called a far pointer, is a 32-bit pointer
- whose arithmetic operations are performed by using just 16 bits. (See the
- summary in Figure 2.) A far pointer can point anywhere in the PC's address
- space, but the 16-bit arithmetic limit means that the memory region towards
- which it points must be less than 64Kb large. For instance, you could use a
- far pointer to access a dynamically allocated array of 25,000 integers, but
- you could not use that same far pointer to access an array of 25,000 floats
- or doubles. This is because the array of integers will take up less than
- 64Kb, while the array of floats or doubles will take more.
-
- A pointer to huge data, called a huge pointer, is also a 32-bit pointer, but
- its arithmetic operations are performed by using all 32 bits. Thus a huge
- pointer can point anywhere, at anything. The trade-off, of course, is that
- huge pointers operate more slowly and use more code than far pointers.
- Adding an integer to a far pointer isn't much more work than adding two
- integers, but adding an integer to a huge pointer is roughly twice the work
- of adding two integers.
-
- Far and huge pointers are sometimes used in small- and medium-model programs
- to access addresses outside the program's 64Kb default data space. They
- might be used, for example, to access the video display buffer or to access
- dynamically allocated memory outside the 64Kb data space. In compact- or
- large-model programs, all data pointers are far pointers, unless you
- specifically ask for a huge or near pointer. In a huge-model program, all
- data pointers are huge unless you specifically request a near or far
- pointer.
-
- When you are declaring an ordinary pointer, such as the pointer ip in
- Figure 1, you place an asterisk in front of the name to signify that *ip is
- an integer; hence ip itself must be a pointer to integer. To declare a far
- or huge pointer, you must use the word far or huge with the asterisk. So
- you would say:
-
- int far *npfi;
- static int huge *hp1, huge *hp2;
-
- In a declaration or a cast, the word far or huge is conceptually glued to
- the asterisk; that's why the word huge appears twice in the second
- declaration, once for each asterisk. Note that the data type and storage
- class, static integer in this example, appear only once in the second
- declaration.
-
- The pointers created in the declaration above are designed to access data
- anywhere in memory, but the memory space for the pointers themselves (4
- bytes each) is allocated in the default data segment. The first pointer is
- named npfi because it is a near pointer to a far integer; the pointer itself
- is near data, but it points at far data. Nevertheless the vernacular term
- for npfi is far pointer, meaning a pointer to far data. You could create a
- near pointer (16-bit pointer) in the far data space, but not in the default
- data segment, by using the following declaration:
-
- int * far fpni;
-
- The pointer in this declaration is named fpni to remind you that it is a far
- pointer to a near integer; the pointer itself (2 bytes) is stored in a far
- data segment even though it can only point at integers inside the default
- data segment. This declaration cannot appear inside a procedure, not even
- inside main, because the declaration specifically tells the compiler to put
- the pointer in the far data segment, whereas automatic variables in a
- procedure are stored on the stack. However, adding the storage class static
- to the declaration would enable it to be placed inside a procedure.
-
- The words near, far, and huge are Microsoft extensions to the C language;
- they help Microsoft C wring the best performance out of the difficult Intel
- architecture. A declaration that looks like
-
- typename far * dataname
-
- creates a pointer in the near data segment to access something stored in a
- far data segment. Conversely, a declaration that looks like
-
- typename far dataname
-
- creates data in the far data segment. If the word far (or near or huge) is
- followed by an asterisk, the pointer is allocated locally, but is used to
- access distant data. If the word far (or near or huge) is followed by the
- name of a variable, then the variable itself is stored in the far (or near
- or huge) data segment. The Microsoft(R) C Compiler User's Guide has a chart
- with several examples of near/far/huge declarations.
-
- The first problem that you may encounter when using far and huge pointers in
- a small- or medium-model program is that the conventional pointer size for
- passing data pointers to procedures in these programs is 16 bits. Thus you
- can't pass a far or huge pointer to a procedure, such as strlen, that
- expects an ordinary near pointer. (If necessary, you can extract a library
- procedure for different models and link it in your program.) And if you
- write your own procedures, be careful to declare the parameter correctly
- as far or huge if you intend to pass it far or huge pointers. Far (or huge)
- and near pointers as procedure parameters simply can't be mixed.
-
- The second problem with far and huge pointers is that the C language
- standard (both the old Kernighan and Ritchie definition and the emerging
- ANSI standard) specifies that the difference between two pointers is an
- integer, and the result of the sizeof operator is also an integer. This can
- cause some problems, which the program in Figure 3 demonstrates.
-
- When the program in Figure 3 is executed with the value 10000 on the
- command line, the output is the following:
-
- Number of Elements: 10000
-
- 1.10000 == (unsigned)(f2-f1)
- 2.10000 == (long)(f2-f1)
- 3.10000 == (unsigned long)(f2-f1)
- 4.10000 == (long)(unsigned)(f2-f1)
-
- As you can see, everything looks OK. However when you increase the size of
- the allocation to 40000, which is a perfectly reasonable size for a
- character array accessed by a far pointer, the result is the following.
-
- Number of Elements: 40000
-
- 1. 40000 == (unsigned)(f2-f1)
- 2.-25536 == (long)(f2-f1)
- 3.-25536 == (unsigned long)(f2-f1)
- 4. 40000 == (long)(unsigned)(f2-f1)
-
- The problem in the second line of the output is that casting the result of
- the pointer subtraction (a signed integer) to a long is done with sign
- extension, thereby producing the wrong answer. The same problem could
- occur with a conventional array containing more than 32,767 elements,
- but conventional 32Kb element arrays are rare, whereas far and huge
- pointers exist specifically to manage large arrays.
-
- The problem in the third line is very subtle, and it took a trip or two to
- the Microsoft(R) C Compiler Language Reference Manual to understand the
- problem. A conversion from a signed integer (the result of subtracting f2
- from f1) to an unsigned long first does a sign extension to long, then
- converts that long to unsigned long. The solution is easy: simply cast the
- signed integer to unsigned, and then cast that to long (the fourth line).
-
- Figure 4 is a program for exploring the pointer arithmetic behavior of huge
- pointers. Since a huge pointer points at a large amount of storage, there
- are a few more cases to consider than with far pointers. When you run the
- program using the value of 10000 as the number of elements, the following
- is printed:
-
- Number of Long Elements: 10000
-
- 1. 59152 == (unsigned)(h2-h1)
- 2. 10000 == (unsigned)(long)(h2-h1)
- 3. -6384 == l1 = h2-h1
- 4. 4294960912 == ul1 = h2-h1
- 5. 10000 == (long)(h2-h1)
- 6. 10000 == (unsigned long)(h2-h1)
- 7. 59152 == (long)(unsigned)(h2-h1)
-
- The only cases that work correctly cast the result of h2-h1 immediately into
- a long or into unsigned long. The Microsoft manual warns you that without a
- cast to long, subtracting two huge pointers will result in an int, with the
- disastrous results shown above. In this particular case, the cast to long
- has an important semantic meaning that goes beyond its usual meaning of
- converting the item to the given type: the cast controls the precision of
- the result when subtracting huge pointers. The rub is that it requires
- Pointer differences to be an int, but an int (on a 16-bit PC) isn't large
- enough.
-
- The other distressing fact that you may have noticed is that the casts that
- allow you to subtract two far pointers are not the same as those that allow
- you to subtract two huge pointers. This means you must be doubly careful
- when you use these seemingly similar types; watch out for subtraction.
-
- Another difficulty with both far and huge pointers is the common assumption
- that pointers are well behaved. With the Intel segmented architecture,
- many different bit patterns allow a given far or huge pointer to access a
- given location. Thirty-two-bit pointers have two pieces, the high 16 bits,
- which are loaded into a segment register, and the low 16 bits, which are an
- offset. In the Intel CPU, the segment register value, shifted left four, is
- added to the ordinary register to form a 20-bit address. Thus the pointer
- 0100:0000H (32-bit Intel pointers are often written with a colon in the
- middle for clarity) will access the location 1000H in memory. This is
- because the high 16 bits (0100H) are shifted left by four (01000H) and then
- added to the low 16 bits (0H) to produce the result 01000H. Figure 5 shows
- two other pointer values that will access location 1000H. The moral here is
- that comparing two pointers for equality may or may not work, depending on
- their bit patterns.
-
- The program in Figure 6 demonstrates the hazards of comparing far and huge
- pointers. The first printf statement will print yes or no, depending on
- whether the two pointers compared are equal. The second printf will print
- yes if the result of subtracting the two pointers is 0; otherwise it will
- print no. With ordinary pointers (PDP-11 pointers or 16-bit Intel pointers),
- the two results will always be the same. However, for two huge pointers
- that point to the same place, even though they have different bit patterns,
- the output of the program is the following:
-
- 0100:0000 == 0080:0800 ? no
- (long)(0100:0000 -0080:0800) == 0L ? yes
-
- This strange result, two unequal pointers that have no difference,
- actually makes perfect sense. The C compiler compares pointers by
- comparing their bit patterns, but it must subtract them more laboriously.
- First the compiler forms two 20-bit linear addresses just as the CPU does
- in hardware, then subtracts the linear addresses and divides by the size of
- whatever the pointer accesses.
-
- If you do conventional things with your far and huge pointers, such as move
- them within an allocated chunk of memory, then comparisons will work fine.
- However, with anything unconventional such as hand-crafting pointers,
- picking them up from hardware devices, or acquiring them from foreign
- subroutine libraries, simple comparison is not wise. You can always compare
- two pointers by using the expression ((long)(p2 - p1) == 0), but it takes
- much longer than the typical expression p2==p1.
-
- You must keep in mind that the bit value in a far or huge pointer is not a
- byte offset from location 0. Instead it is in the Intel segment:offset
- format that must be added together (see Figure 3) to form a byte offset from
- location 0. Most reasons for converting a pointer to a byte offset from 0
- are very bad, but if you must, see Figure 7 to do it correctly. The output
- of the program in Figure 7 is shown in Figure 8.
-
- Conventional pointers are often cast to an unsigned, or to an unsigned long,
- when a byte offset from 0 is required. This will not work with a far or huge
- pointer, because of the segment:offset nature of the Intel architecture.
- The hptr2long macro is necessary to make sense of the address coded in the
- pointer. (The FP_SEG, FP_OFF macro calls in the hptr2long macro extract the
- segment and offset parts of a huge pointer. They are defined in the dos.h
- include file.)
-
- Now let's delve into the relative efficiency of far and huge pointers.
- Before looking at some assembly language code generated by the compiler for
- far and huge operations, let's think about what must be done in the two
- cases. For a far pointer, the C compiler can access something that it
- points towards by loading its high 16 bits into a segment register (usually
- ES), loading its low 16 bits into an ordinary register, and then accessing
- that location. For huge pointers the operation is the same.
-
- However, pointer arithmetic with far pointers is much simpler than that for
- huge pointers. For a far pointer, all arithmetic is done with just the low
- 16 bits. All the compiler has to do is operate on the low 16 bits, just as
- if they were an ordinary near pointer. However, for a huge pointer the
- compiler must first do the operation on the low 16 bits, then it must shift
- the carry or borrow left 12 bits, and then it must perform the operation on
- the high 16 bits. That's a lot of work, which is sometimes assisted by
- subroutine calls to library routines.
-
- The program in Figure 9 invokes the four most common pointer operations:
- addition of an integer to a pointer, dereferencing a pointer, comparing
- two pointers, and subtracting two pointers. The code comes from one of my
- programs that manages a huge array using near and far pointers. The
- #defines at the top of the program allow it to be easily compiled using
- near, far, or huge pointers by defining either the word FAR or HUGE on the C
- compiler command line. The code generated for each pointer type is shown in
- Figure 10.
-
- The most striking feature of Figure 10 is that the overhead for using near
- and far pointers is similar; the heavy penalty does not occur until you
- start to use huge pointers. The most burdensome operation on huge pointers
- is subtraction; it takes nine in-line instructions, including one
- subroutine call to a support routine.
-
- In my work I needed huge pointers to manage an array of 50,000 long
- variables. Unfortunately the program calculated too slowly, so I was
- forced to speed things up. In my program most calculations only access a
- small region of the array, small enough to be accessed with a far pointer,
- although the array itself is so large that it has to be managed with huge
- pointers.
-
- My solution was to create a routine that would convert a huge pointer into a
- far pointer optimally. In my application, this meant that the resulting far
- pointer could be incremented and/or decremented several thousand times
- without having the low 16 bits overflow or underflow. Meeting that
- condition implies that the low 16 bits should be neither too small (danger
- of underflow) nor too large (danger of overflow).
-
- Suppose you have the huge integer pointer 2000:0000H. If you decrement it,
- the compiler will decrement the low 16 bits and then propagate the borrow
- into the high bits, yielding the correct answer: 1000:FFFEH. However if
- 2000:000H is a far integer pointer and you decrement it, the compiler will
- simply decrement the low 16 bits, yielding a wrong answer: 2000:FFFEH. Far
- pointer arithmetic only breaks when there is an overflow or a borrow, so
- that far pointers whose low 16 bits are far from 0H and far from FFFFH (that
- is, in the neighborhood of 8000H) are quite safe.
-
- I wrote a routine called huge2far (see Figure 11) that rearranges a huge
- pointer to make its offset close to 8000H. This creates a far pointer that
- can access any element within a few thousand elements of where the pointer
- points initially. In other applications the optimal structuring of a near
- pointer might be different. For instance, in an application where the near
- pointer was only incremented (never decremented), an optional structure
- would make the low 16 bits as close to 0 as possible.
-
- Note that the huge2far routine doesn't work for all possible pointers. For
- example, there is only one pointer, 0000:0000H, that will access location 0.
- But in practice huge pointers always point beyond the program text and data,
- and for addresses in those regions the routine works as claimed.
-
- I use huge pointers in my program to manage the array, but whenever an
- intensive, local calculation is performed I convert a huge pointer into a
- far, do the calculation, and then, if the resulting pointer value must be
- saved, I convert the far pointer back into a huge by using a cast. It
- wouldn't be worthwhile to convert a huge to far to make a single array
- access, but if you want to sum the next thousand numbers in the array, you
- can convert huge to far and then use the far pointer a thousand times to
- access the next thousand array members. The time saved in my program is
- significant.
-
- The code in Figure 10 gives some additional clues about writing more
- efficient programs with huge pointers. Note that the most expensive
- operation, subtraction, is often used in loop bounds checks. Code such as
- the following is very common in C programs (p, q, head, and tail are
- pointers):
-
- while(p++<q)
- ∙
- ∙
- ∙
- for(p=head;p<tail;p++)
- ∙
- ∙
- ∙
-
- If these are huge pointers, each loop expends a lot of time comparing two
- pointers in each iteration. A more efficient solution would avoid the
- comparison on each pass through the loop, as in the following:
-
- cnt = q - p;
- while(p++, cnt--)
- ∙
- ∙
- ∙
- for(cnt=tail-p, p = head;
- cnt > 0; cnt--, p++)
- ∙
- ∙
- ∙
-
- Of course, you have to be careful that the value of p is not changed in the
- body of the loop.
-
-
- Figure 1: Understanding Pointers
-
- main()
- {
- int *ip, i;
-
- ip = &i;
- ip += 100;
- printf("ip - &i: %d\n", ip - &i;
- printf("(unsigned)ip - (unsigned)&1: %u\n",
- (unsigned)ip - (unsigned)&i);
- }
-
-
- Figure 2: Microsoft C Version 4. 0 Data Pointers
-
- Near Far Huge
-
-
- Size (bits) 16 32 32
-
- Addressing Anywhere in Anywhere in Anywhere
- 64Kb default any 64Kb data
- data segment segment
-
- Pointer
- arithmetic 16 bits 16 bits 32 bits
-
- Microsoft C Small Compact Huge
- defaults Medium Large
-
-
- Figure 3: Pointer Subtraction
-
- /*
- * explore far pointers
- */
- #include <malloc.h>
- #include <stdlib.h>
-
- main(c,v)
- char *v[];
- {
- unsigned nelems;
- char far * f1, far * f2;
-
- if (c != 2) {
- printf("usage: %s elements_to_allocate\n", v[0]);
- exit();
- }
- nelems = (unsigned)atol(v[1]);
-
- f1 = (char far *)_fmalloc(nelems * sizeof(char));
- if (f1 == (char far *)0) {
- perror("_falloc failed:");
- exit();
- }
- f2 = f1 + nelems;
-
- printf("Number of Elements: %u\n", nelems);
- printf("1. %6u == (unsigned)(f2-f1)\n", (unsigned)(f2-f1));
- printf("2. %6ld == (long)(f2-f1)\n", (long)(f2-f1));
- printf("3. %6ld == (unsigned long)(f2-f1)\n",
- (unsigned long)(f2-f1));
- printf("4. %6ld == (long)(unsigned)(f2-f1)\n",
- (long)(unsigned)(f2-f1));
- }
-
-
- Figure 4: Pointer Arithmetic Using Huge Pointers
-
- /*
- * explore huge pointers
- */
- #include <malloc.h>
- #include <stdlib.h>
-
- main(c,v)
- char *v[];
- {
- long nelems;
- long huge * h1, huge * h2;
- long l1;
- unsigned long ul1;
-
- if (c != 2) {
- printf("usage: %s elements_to_allocate\n", v[0]);
- exit();
- }
- nelems = atol(v[1]);
-
- h1 = (long huge *)halloc(nelems, sizeof(long));
- if (h1 == (long huge *)0) {
- perror("halloc failed:");
- exit();
- }
-
- h2 = h1 + nelems;
- printf("Number of Long Elements: %lu\n", nelems);
- printf("1. %12u == (unsigned)(h2-h1)\n", (unsigned)(h2-h1));
- printf("2. %12u == (unsigned)(long)(h2-h1)\n",
- (unsigned)(long)(h2-h1));
- printf("3. %12ld == l1 = h2-h1\n", l1=h2-h1);
- printf("4. %12lu == ul1 = h2-h1\n", ul1=h2-h1);
- printf("5. %12ld == (long)(h2-h1)\n", (long)(h2-h1));
- printf("6. %12lu == (unsigned long)(h2-h1)\n", (unsigned
- long)(h2-h1));
- printf("7. %12ld == (long)(unsigned)(h2-h1)\n",
- (long)(unsigned)(h2-h1));
- }
-
-
- Figure 5: Three Distinct Pointer Values Point to Location 1000H
-
- Pointer 0x01000000 0x00ff0010 0x00800800
-
-
- High 16 bits 0x0100 0x00ff 0x80
-
-
- Low 16 Bits 0x0 0x10 0x800
-
-
- Intel segmentation 0x01000 0x00ff0 0x800
-
- Address computation +0x0 +0x10 +0x800
- ________ ________ __________
- Result 0x01000 0x01000 0x1000
-
-
- Figure 6: Comparing Far and Huge Pointers
-
- /*
- * equality of huge pointers
- */
-
- main()
- {
- int huge *h1, huge * h2;
-
- h1 = (int huge *)0x01000000;
- h2 = (int huge *)0x00800800;
-
- printf("%p == %p ? %s\n", h1, h2, h1 == h2 ? "yes" : "no");
- printf("(long)(%p - %p) == 0L ? %s\n", h1, h2,
- (long)(h1 - h2) == 0L ? "yes" : "no");
- }
-
-
- Figure 7: Converting Pointers to a Byte Offset from 0
-
- /*
- * Byte offsets of huge pointers
- */
-
- #include <dos.h>
- #include <malloc.h>
-
- /* convert a MSC huge pointer into a byte offset from location 0 */
- #define hptr2long(p) (((unsigned long)FP_SEG(p))<<4 + (unsigned
- long)FP_OFF(p))
-
- #define NELEM 5
-
- main()
- {
- int huge *hptr[NELEM];
- int i;
-
- printf(" Huge Offset\n");
- printf(" Pointer from 0\n");
- for(i=0;i<NELEM;i++) {
- hptr[i] = (int huge *)halloc(32768L, sizeof(int));
- printf("%p %7ld\n", hptr[i], hptr2long(hptr[i]));
- }
- }
-
-
- Figure 8: The output of the program in Figure 7. The first pointer,
- 209A:0000H, accesses byte 209A0H on the PC. Note that 209A0H is
- equal to 133536 decimal.
-
- Huge Offset
- pointer from 0
-
- 209A:0000 133536
- 309B:0000 199088
- 409C:0000 264640
- 509D:0000 330192
- 609E:0000 395744
-
-
- Figure 9: Invoking Common Pointer Operations
-
- #include <malloc.h>
-
- if defined (HUGE)
- #define PTYPE Huge
- #define MALLOC halloc(NELEM, sizeof(unsighned) )
- #define DTYPE long
- #elif defined (FAR)
- #define PTYPE far
- #define MALLOC _fmalloc(NELEM * sizeof(unsigned) )
- #define DTYPE unsigned
- #else
- #define PTYPE
- #define MALLOC malloc(NELEM * sizeof(unsighned) )
- #define DTYPE unsigned
- #endif
-
- main()
- {
- unsigned PTYPE *head, PTYPE *aspikes, PTYPE *end;
- unsigned time;
- DTYPE diff;
-
- head = (unsigned PTYPE *)MALLOC;
- aspikes = head;
- end = head + NELEM;
- while ((time = *aspikes) && aspikes++ < end)
- diff = (DTYPE) (end - aspikes);
- }
-
-
- Figure 10: Compiler Code Generation for the Program in Figure 9 for Near,
- Far, and Huge Pointers
-
- ╓┌────────────────────┌─────────────────────────┌────────────────────────┌───
- C NEAR FAR HUGE
-
- end = head + NELEM; add ax,20000 add ax,20000 mov a
- mov [bp-6],ax ;end mov [bp-8],ax ;end cwd
- mov [bp-6],dx add a
- adc d
- mov c
- C NEAR FAR HUGE
- mov c
- shl d
- add d
- mov [
- mov [
-
- time = *aspikes mov bx,[bp-8] ;aspikes les bx,[bp-12] ;aspikes les b
- mov ax,[bx] mov ax,es:[bx] mov a
- mov [bp-4],ax ;time mov [bp-4],ax ;time mov [
-
- aspikes++ < end mov ax,[bp-8] ;aspikes mov ax,[bp-12] ;aspikes mov a
- add WORD PTR [bp-8],2 mov dx,[bp-10] cwd
- cmp ax,[bp-6] ;end add WORD PTR [bp-12],2 add a
- cmp ax,[bp-8] ;end adc d
- mov c
- shl d
- add d
- mov c
- mov b
- mov [
- C NEAR FAR HUGE
- mov [
- mov [
- cmp c
-
- diff = (DTYPE)(end-
- aspikes); mov ax,[bp-6] ;end mov ax,[bp-8] ;end pushd
- sub ax,[bp-8] ;aspikes sub ax,[bp-12] ;aspikes pusha
- sar ax,1 sar ax,1 pushW
- mov [bp-2],ax ;diff mov [bp-2],ax ;diff pushW
- sar d
- rcr a
- mov [
- mov [
-
-
-
- Figure 11: Rearranging Huge Pointers
-
- #include <dos.h>
-
- char far *
- huge2far(n)
- char huge *n;
- {
- register unsigned o;
- register unsigned s;
- unsigned d;
- char far *p;
-
- o = FP_OFF(n);
- s = FP_SEG(n);
- if (o < 0x7ff0) {
- d = (0x8000 - o) & ~0xf;
- o += d;
- s -= (d>>4);
- }
- else if (o > 0x8000) {
- d = (o - 0x7ff0) & ~0xf;
- o -= d;
- s += (d>>4);
- }
- FP_OFF(p) = o;
- FP_SEG(p) = s;
- return p;
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- EMS Support Improves Microsoft Windows 2.0 Application Performance
-
- ───────────────────────────────────────────────────────────────────────────
- Also see the related article:
- Glossary of Window Memory Terms
- ───────────────────────────────────────────────────────────────────────────
-
- Paul Yao
-
- In 1981, IBM introduced its Personal Computer, which, among other things,
- gave applications developers more memory to work with. You may recall that
- the best-selling machine at that time was the Apple(R) II, which, with its
- 6502 processor was able to address 64Kb of RAM memory. The IBM(R) Personal
- Computer was able to address a full megabyte of memory with its Intel(R)
- 8086 processor. Subtracting the memory reserved for the hardware, the
- program address space of 640Kb was ten times the available memory on the
- Apple computer. Suddenly, programmers and users found themselves with more
- memory than they knew what to do with.
-
- Eventually, users pushed the limits of 640Kb of memory: software became
- larger as new features were added, spreadsheets and documents grew as users
- became more sophisticated, and, with advances in chip technology, the
- price of memory dropped. What once had seemed an enormous amount of memory
- was no longer enough.
-
- To meet the need for more memory, the Expanded Memory Specification (EMS)
- was introduced in 1984 to provide applications with memory above the 640Kb
- limit. EMS defines the software interface for a program to access expanded
- memory. Using EMS, an application can store up to 8 additional megabytes of
- data in RAM. AST Research Inc. developed a superset of EMS, called the
- Enhanced Expanded Memory Specification (EEMS), which provided even greater
- flexibility in the way that expanded memory was mapped for use by
- applications.
-
- The EMS and EEMS specifications were superseded by the Lotus/Intel/
- Microsoft Expanded Memory Specification Version 4.0 (LIM EMS 4.0) in August
- 1987. EMS 4.0 provides up to 32Mb of expanded memory, four times the 8Mb
- supported under EMS 3.2. By consolidating two similar, but distinct, memory
- specifications, LIM EMS 4.0 simplifies expanded memory support for Windows
- since any EMS or EEMS card can be made compatible with LIM EMS 4.0 by adding
- a device driver. Besides unifying the expanded memory interface, EMS 4.0
- provides multitasking support for expanded memory.
-
- This article looks at how expanded memory works, how Windows 2.0 uses
- expanded memory, how virtual memory is implemented in Windows/386, and how
- expanded memory support will affect your Windows programming.
-
-
- Expanded Memory
-
- EMS 4.0 allows applications to go beyond the 640Kb limit by setting aside
- part of the 8088's 1Mb address space as a porthole into expanded memory,
- called a page frame. Depending on how system memory is used, page frames
- range in size from 16Kb to 1024Kb.
-
- Page frames are divided into physical pages, which are typically 16Kb.
- Expanded memory is divided into logical pages, also 16Kb in size.
- Applications obtain expanded memory by asking the Expanded Memory
- Manager (EMM) to allocate a certain number of logical pages. The application
- must ask the EMM to map a set of logical pages into the physical address
- space of the hardware in order to use them. The application then accesses
- expanded memory just as it would access conventional memory. To use another
- set of logical pages, the application asks the EMM to map in a new set of
- pages. An application cannot access a page when that page is not mapped in.
- While the physical address space of the 8088 is 1Mb, expanded memory can
- have up to 32Mb.
-
- Figure 1 is a memory map showing the relationship of an MS-DOS(R)
- application, such as Lotus(R) 1-2-3(R), to expanded memory logical pages. In
- this example, there are two slots in the physical address space, labeled
- Slot-1 and Slot-2. The two logical pages, which are labeled Page-3 and Page-
- 6, are currently mapped into the two slots. Note that the shaded areas are
- reserved for the operating system and for hardware use (where display
- memory is located). The single page frame resides above 640Kb, although EMS
- 4.0 allows expanded memory to be mapped into any portion of the physical
- address space.
-
- Expanded memory requires special hardware and special software. The hardware
- consists of a card that contains memory chips. The software is a device
- driver, the EMM, which processes the requests for expanded memory actions
- and makes the expanded memory board perform its mapping magic.
-
- Applications must allocate memory by calling the EMM, and by another call
- that causes the allocated page to be made accessible by hardware mapping.
- The third task that applications must perform is to free allocated expanded
- memory once it is no longer needed. These three steps are analogous to the
- way that memory is allocated from the Windows memory manager: memory is
- allocated (either with GlobalAlloc or LocalAlloc), it is mapped into a
- physical address (via GlobalLock or LocalLock), and it is released when no
- longer needed (by GlobalFree or LocalFree). The fourth step, unlocking
- memory (with GlobalUnlock or LocalUnlock), is analogous to the unmapping of
- expanded memory pages, which in EMS is part of the mapping call.
-
- Some EMS boards allow only a single 64Kb page frame, which must physically
- appear above 640Kb. Other boards permit multiple page frames with no limit
- on the size or location of the page frames. Even though an EMS 4.0 driver
- can be written for either type of board, their capabilities require that
- Windows deal with each a little differently. Older boards leave the memory
- below 640Kb untouched and provide a relatively small page frame. Newer
- boards can be configured to take over everything above 256Kb, allowing total
- bank space to exceed 600Kb.
-
- As I will discuss later, the size of the available page frame affects how
- the Windows memory manager uses expanded memory. I will refer to old-style
- EMS support, which allows only a single page frame, as "small frame" EMS and
- to the new-style EMS support, which permits multiple page frames, as "large
- frame" EMS. Note that the presence of terminate-and-stay-resident programs,
- network device cards, and other dedicated memory applications, may cause
- Windows to use a newer EMS card in "small frame" EMS mode.
-
- The division between the bankable and the nonbankable portions of a given
- EMS memory configuration is called the "bank line." Knowing where objects
- will be allocated ("above the bank line" or "below the bank line") will not
- affect you if you are writing a single, standalone Windows application.
- But if you develop applications that "talk" to each other, write your own
- dynamic-link library, use Windows hooks, or wish to use the clipboard or
- DDE, you should familiarize yourself with the location of memory objects
- relative to the EMS bank line.
-
- The mapping and unmapping of logical expanded memory pages does not require
- copying large blocks of data. Instead, it involves modifying a few mapping
- registers. Once these mapping registers are changed, memory mapping
- automatically occurs on the expanded memory card and is thus fast and
- efficient, with little performance degradation.
-
-
- Expanded Memory and Windows
-
- Windows Version 2.0 uses expanded memory to bank Windows applications. This
- expanded memory support lets multiple large Windows applications, such as
- Microsoft Excel and Aldus PageMaker(R), run simultaneously and as fast as if
- each were running in Windows by itself. Windows 1.x uses expanded memory to
- swap typical MS-DOS applications, but not for swapping Windows applications.
- Windows 2.0 will support these older applications, but with the advantage of
- banking Windows applications in expanded memory.
-
- Memory is the most severely constrained resource in Windows. There are two
- reasons for this: one due to the hardware, and the other related to the
- multitasking nature of Windows. The hardware constraint is simply that the
- address space of the 8086 family of processors is 1Mb. The multitasking
- nature of Windows means that the demand on memory is potentially
- unlimited, as more and more applications are run. This problem is
- partially solved in the design of Windows, which provides a type of virtual
- memory support. Unnecessary code and resource segments marked
- "discardable" and which are "least recently used," can be destroyed on
- demand, freeing up memory. Of course, when the destroyed objects are needed
- again, Windows will re-read them from the disk (code and resource
- segments in memory are "read-only," which means that the disk image is
- guaranteed to be correct). The memory problem is not entirely solved by
- discarding. Each application has a minimum memory requirement, so that
- two or more large applications may not be able to run simultaneously. This
- is a problem because users will most likely want to use the large, powerful
- applications together, to cut and paste data, initiate DDE conversations,
- and in general to take advantage of Windows' ability to move quickly between
- applications.
-
- Windows 2.0 solves the problem with its enhanced memory manager, which will
- bank each application into its own set of expanded memory pages, so that
- each application is, in effect, running on the machine by itself. This works
- as long as an EMS card and an EMS 4.0 driver are present. The banking of
- application code and data is entirely transparent to Windows programs. The
- Windows memory manager performs all required handshaking with the EMM to
- allocate memory, map and unmap pages, and to free EMS memory when an
- application is closed.
-
- The memory manager could have been enhanced to page in EMS memory so as to
- give a single Windows application the full 32Mb of memory that EMS 4.0
- supports. The current design was chosen, however, for implementation
- considerations, performance goals, and the availability of alternative
- solutions.
-
- One implementation consideration in favor of this design is the need for
- Windows 2.0 to be backwards compatible with Windows 1.x applications. This
- means that the interface to the memory manager must be the same. To make use
- of EMS memory, a new interface would be required. However, this interface
- already exists──in the form of the EMS 4.0 specification. Therefore, if an
- application has a need for megabytes of memory, it can talk directly to
- the EMS memory manager.
-
- The second reason for the current design is to meet the main performance
- goal for supporting expanded memory: allowing several large Windows
- applications to run simultaneously, with minimum overhead incurred at
- context-switching time. The current implementation insists only that each
- application be able to run with acceptable performance in 640Kb. After all,
- not every computer will have expanded memory, and thus Windows' developers
- cannot assume its existence, except, perhaps, in vertical-market packages
- where a turnkey system was being developed.
-
- The third reason that Windows EMS support does not provide megabytes of
- expanded memory for applications is that a disk cache serves the same
- purpose. Code, resource, and other discardable segments can be destroyed
- and quickly re-read from the disk cache using the loading and discarding
- capabilities of the memory manager. Windows 2.0 includes a disk cacher,
- which dynamically allocates and frees EMS pages as needed. Unlike a RAM
- drive, optimum performance of the disk cache does not depend on a user
- decision.
-
- Windows 2.0, unlike the earlier versions, will run in the compatibility box
- of OS/2. Even in this mode, Windows will use expanded memory.
-
-
- How EMS Support Works
-
- The Windows memory manager is responsible for controlling the dynamic
- allocation of memory from the global heap, as well as allocation from
- each task or library's local heap. Here, we are only interested in memory
- allocated from the global heap. The Windows memory manager arbitrates
- requests for blocks of memory, handling all the adminstrative overhead when
- blocks must be moved or discarded to satisfy allocation requests. When a
- block of memory is freed, that is, returned to the available pool of
- memory, the Windows memory manager makes a note of this.
-
- Windows pages in a fresh bank of EMS memory for every application that is
- loaded. Figure 2 is a memory map showing three Windows applications; Write,
- PageMaker, and Microsoft Excel, each in its own bank of expanded memory
- pages. In Figure 2, the Microsoft Excel pages are all currently mapped into
- physical addresses. Note that only the minimum number of expanded memory
- logical pages are allocated for a given application, so that expanded
- memory can be used as efficiently as possible.
-
- Figure 2 shows that all of the pages in Microsoft Excel are mapped in. This
- happens whenever Microsoft Excel is processing, or, in Windows terms,
- whenever Microsoft Excel receives a message via the call to GetMessage. If
- the user clicks on the PageMaker window, a context switch makes PageMaker
- the active application. During context switching, the memory manager
- will bank in all of the PageMaker expanded memory pages, causing all of the
- Microsoft Excel pages to be banked out. This has implications for sharing
- memory among applications, which will be discussed later.
-
- While the application is processing, the allocation of memory banks
- remains fixed. This means that Windows applications are still running in
- 640Kb (actually up to 256Kb more, or 896Kb, with a 64Kb EGA card installed),
- and should be written to obtain satisfactory performance within this
- memory.
-
- The only objects that will be discarded are those that appear in the
- physical address space, that is, in either the currently mapped EMS memory
- or below the bank line. Memory objects that are banked out are never
- discarded.
-
-
- Memory Protection
-
- When an application's EMS memory is not mapped into the physical address
- space, it is protected from other "misbehaving" applications. When Windows
- is running with large frame EMS, that is, when expanded memory pages can be
- mapped into any part of the physical address space, each application is
- completely protected from others. When small frame EMS is running, only
- those code segments that are in EMS memory are protected. Although this
- memory protection is not as complete as that of OS/2, in which illegal
- addressing results in a general protection fault, it does reduce the
- damage in a way that Windows 1.x did not.
-
-
- Memory Managers
-
- While Windows is running with EMS memory, there are really two memory
- managers present: the Windows memory manager and the EMM. The Windows memory
- manager is the primary memory manager under Windows 2.0, calling on the EMM
- only to control expanded memory. Its task is to allocate, map and unmap, and
- free expanded memory, plus keep track of the EMS registers. Because there
- are two separate memory managers, a Windows application can call the EMM
- directly once it knows that EMS is present, to allocate memory on its own.
-
- One thing you'll want to know is which objects are allocated above the bank
- line and are therefore bankable, and which are allocated below the bank line
- and are thus shareable. This discussion is complicated by the fact that EMS
- 4.0 supports two types of page frames: small and large. The small page frame
- for EMS memory is 64Kb, while the large page frame for EMS memory can be up
- to 896Kb, given the proper hardware setup. Because almost the entire
- address space above 256Kb can be banked by using the large page frame, it
- offers the most flexibility to the Windows memory manager. Figure 3 shows
- how expanded memory is used for both types of page frames.
-
- More than one instance of a given application, in the current
- implementation, will share a given bank (although each instance will have
- its own data segment), thus preventing any problems in the sharing of
- instance data, either explicitly through GetInstanceData or implicitly
- through sharing memory handles. Future versions may offer developers the
- option of requesting a separate EMS bank for each instance of an
- application.
-
- Certainly large frame gives the memory manager the most flexibility, but
- even with small frame there are significant advantages to EMS support. For
- small applications, all of the code and resources will fit into banked
- memory, allowing many small applications to run with little performance
- degradation. Applications like the control panel, the calculator, or the
- calendar can be loaded together with a large application such as Microsoft
- Excel with a minimum of interference. Small frame EMS support, then, allows
- the user to take better advantage of the multitasking abilities in Windows.
-
-
- Dynamic-Link Libraries
-
- When small frame EMS is present, only the resources and code segments from a
- task are loaded into the EMS banks. With large frame EMS the memory manager
- places several other types of objects above the line in the EMS banks. These
- include task data segments, library discardable code segments, and memory
- allocated with GlobalAlloc. These last two deserve some attention because
- they raise important implementation issues.
-
- Dynamic-link libraries are accessible from any task. Examples of dynamic-
- link libraries under Windows include Kernel, User, and GDI, as well as all
- device drivers (keyboard, mouse, timer, display, and printer). Clearly,
- library routines, such as the GDI routine TextOut, must be accessible from
- any task at any time. When large frame EMS is present, the code in dynamic-
- link libraries is handled in one of two ways: fixed code segments live below
- the line, discardable code segments live above the line.
-
- Large frame EMS support places library discardable segments above the line
- in the bank of the calling task, creating a possibility that at any time a
- given library code segment may appear in multiple banks. For example,
- multiple copies of the dialog box control manager, which contains the window
- procedures for the edit, push button, and other dialog box controls, might
- concurrently reside in EMS memory. Although there is redundancy, the net
- result is that applications have access to the library segments that they
- need, and little room is taken up in the scarce portion of memory below the
- bank line.
-
- Ordinarily, when dynamic-link libraries allocate memory with GlobalAlloc and
- a large EMS frame is present, the allocation occurs above the line. There
- are times, however, when it is necessary for a library to have its global
- memory below the line. Some libraries may have global memory objects that
- they need to access at all times. In such a case, the GMEM_NOT_BANKED flag
- is used in the GlobalAlloc call.
-
- Windows 1.x printer drivers run into compatibility problems because they do
- not use the GMEM_NOT_BANKED flag. Printer drivers tend to be the only
- libraries that are loaded via the LoadLibrary routine; other libraries are
- loaded as "dependent modules" of tasks. (A dependent module is a library
- that must be present for a task to execute.) Libraries loaded with
- LoadLibrary allocate global memory below the EMS bank line, while dependent
- modules, by default, allocate global memory above the bank line.
-
-
- Changes to the API
-
- Only a single routine, LimitEMSPages, has been added to the Windows API.
- This routine allows an application to control the number of EMS pages that
- Windows allocates on its behalf. It does not, however, limit the number of
- pages that an application can allocate for itself from the EMS memory
- manager.
-
- When you want to run Windows with EMS memory disabled, put the /n switch
- after the WIN command at the DOS prompt:
-
- C> win /n
-
-
- Virtual Memory
-
- The biggest advantage of Windows/386 is its support of standard MS-DOS
- applications. The current implementation of Windows/386 creates multiple
- virtual machines (VMs), each of which behaves as an independent 8086.
- Because each is effectively given its own 8086 machine with 640Kb of address
- space, applications can run independently without interfering with each
- other. Also, because of the ability of the Intel 80386 to intercept direct
- memory mapped video output, each MS-DOS application can run in its own
- window on the screen.
-
- Note, however, that Windows/386 does not give each Windows application its
- own 640Kb partition; instead, all Windows applications run in the same 640Kb
- partition. This means that Windows applications do not have the same memory
- advantages that MS-DOS applications have running under Windows/386.
-
- Memory banking support in Windows/386 is the same as EMS support in Windows
- 2.0. Although it works a little differently, because the 386 has hardware
- support for memory management, the differences will be transparent to the
- Windows application.
-
-
- Programming Guides
-
- The programming guidelines listed here should be no surprise to Windows
- programmers, who have been told to be good citizens since they started
- writing their first window procedure. It is useful to reiterate them,
- however, because Windows EMS support under Windows 2.0 and virtual memory
- support under Windows/386 require that you follow them strictly. These
- guidelines will apply to both, but a few are specific to the addition of
- EMS support and will help you ensure that your applications run in both
- Windows 2.0 and Windows/386.
-
-
- Efficient Memory Use
-
- There are no current plans for Windows memory management to allow Windows
- applications to allocate past the 1Mb physical address space of the 8086.
- Instead, Windows performs bank switching of memory, so each application
- behaves as if it had the entire computer to itself.
-
- The Windows memory manager lets you specify that memory allocated from the
- global heap, when EMS is running, be below the bank line, so that it is
- always visible from all applications. You do this with the GMEM_NOT_BANKED
- flag to the GlobalAlloc call. You should regard this as a method of last
- resort for sharing memory, because memory below the line is very scarce and
- is only used for the most critical system objects. Unless you have a very
- compelling reason, do not perform this type of allocation. A good example
- of a global allocation that must be below the line is the allocation
- required by a printer driver.
-
-
- Code Structure
-
- To optimize the performance of your application with small frame EMS, mark
- the most important segments as PRELOAD in the module definition (.DEF)
- file, and list their names at the beginning of the SEGMENTS list.
- Performance is improved because, when Windows starts loading an application
- into memory, it starts with the bankable EMS memory. Only after the EMS
- bank is filled does Windows start using space below the line. Performance
- improves with small frame EMS because the memory manager will not discard
- objects from above the bank line.
-
-
- Data Sharing
-
- The clipboard, DDE, and libraries are methods of data sharing guaranteed to
- work in current and future versions of Windows. Other than the clipboard
- and DDE, you should not share memory by passing global memory handles
- between applications. Passing long pointers to data objects should also be
- avoided. You can't be sure that the target data will be banked in when the
- receiving application is processing. Applications that ignore this
- guideline will not run under planned versions of Windows 2.0 or
- Windows/386.
-
- Be sure to copy the data from the clipboard into your own data areas before
- you close and release the clipboard. Windows supports EMS by copying
- clipboard objects that have been banked out into the currently banked
- memory. After the clipboard is closed, these objects will be destroyed,
- rendering them inaccessible to the receiving application.
-
- When an application receives a global handle through the clipboard or DDE,
- it must check the return value to the GlobalLock call. The Windows memory
- manager copies into the current bank those DDE or clipboard objects that
- reside in unbanked EMS memory. If the memory manager is not able to perform
- this copying (such as in a low memory situation), it informs the receiving
- application by returning null to GlobalLock.
-
-
- Code Sharing
-
- One innovation of Windows is its ability to share code. Only a single copy
- of any given Windows routine is loaded into memory at any given time. It
- can be accessed, via the dynamic linking mechanism, by several applications.
- Window procedures are shared among multiple instances of windows and fine-
- tuned through subclassing. If you write well-behaved Windows applications,
- code sharing by EMS memory is not affected: since several instances of an
- application run in the same bank of EMS memory, there is no problem in
- sharing code, data, or resources.
-
- Dynamic-link libraries are one method for sharing code among several
- cooperating applications. Windows loads library discardable segments above
- the bank line, so there can be multiple copies of some code segments in EMS
- memory. This redundancy can be avoided by converting the library into a
- windowless task and communicating with the calling applications via the
- various message posting routines (PostMessage and PostAppMessage).
-
- You can never execute code that belongs to another task by calling it
- directly. In Windows routine terms, you can never call GetProcAddress on
- another task in order to directly call the target code yourself. Although it
- worked in Windows 1.x, the Windows 2.0 memory manager won't let you do this.
- Instead, use the SendMessage routine to perform intertask calls.
-
- An application can directly call the EMM to allocate EMS memory, provided it
- follows EMS 3.2 guidelines; it may use the reallocation function (function
- 17) from LIM EMS 4.0. Windows must be notified so that it does not try to
- use the 64Kb EMS 3.2 page frame; this is achieved by adding the switch
- -LIM32 to the call to the resource compiler. Windows avoids putting code in
- the 64Kb EMS 3.2 window for your application, but it can still use this
- page frame to bank other applications.
-
-
- Global Memory
-
- You should not allocate global memory below the line, that is, you should
- not use the GMEM_NOT_BANKED switch to GlobalAlloc except as a final resort.
- When global memory objects are shared among applications, the call to
- GlobalLock takes care of copying the appropriate blocks of memory, as
- necessary. Of course, this should only be used with the clipboard and DDE
- methods to guarantee compatibility with future versions of Windows 2.0,
- most notably, Windows/386.
-
- Your program should always check for failure in allocating memory,
- reallocating memory, and locking memory. This is critical when using DDE or
- the clipboard to share data, and it is generally a good Windows programming
- practice. An invalid far pointer can be deadly, since it allows data to be
- written or read from anywhere in the computer's address space.
-
-
- Windows Hooks
-
- Windows hooks allow you to trap certain types of events before they are
- placed into the system queue. For example, you can write a keyboard hook to
- watch for the <Alt>-P key, which may cause a background screen capture
- program to print the screen on the default printer.
-
- The impact of EMS memory on Windows hooks requires some extra programming
- effort on your part. First of all, Windows hooks must appear below the bank
- line and should be placed in fixed library seg-ments. Also, because a
- window hook has no way of knowing whether the initiating task is currently
- banked in, it must communicate with the initiating task via messages (most
- likely SendMessage).
-
-
- Debugging Support
-
- You can now use CodeView(R), Microsoft's premier debugging tool, to debug
- Windows 2.0 applications. This is a welcome improvement over Symdeb, a
- powerful but cryptic debugging tool that appeals mostly to assembly language
- programmers. CodeView requires either a monochrome monitor or a dumb
- terminal, because when Windows is running it takes over the screen. Another
- hardware requirement for using CodeView is that you must have EMS memory
- installed since CodeView talks directly to the expanded memory manager to
- minimize the amount of memory taken from the program being debugged.
-
- To facilitate debugging when EMS is present, you may want to place the
- following switch in your WIN.INI file:
-
- [kernel]
- EnableEMSDebug=1
-
- which allows you to set breakpoints within discardable library code. Recall
- that there can be multiple copies of discardable library code segments,
- each in a different EMS bank. This switch tells the memory manager to update
- the debugger's table of library discardable segments during every context
- switch. Any debugger that works with Windows, including Symdeb, CodeView,
- Answer, or Atron will work properly with EMS.
-
-
- Conclusion
-
- For the most part, expanded memory support under Windows will be
- transparent to the programmer. With the exception of a single new Windows
- routine, LimitEMSPages, a few flags, and resource compiler switches, the
- interface that Windows programmers work with has not changed. What has
- changed, however, is that there must be stricter adherence to interactions
- between tasks. Data sharing, for example, must be performed with one of the
- three supported methods: the clipboard, DDE, or via a shared dynamic-link
- library. Directly calling into another task's code, which is never a good
- idea, is not possible with the Windows 2.0 memory manager.
-
- Programmers will benefit most from the improved performance of Windows 2.0
- when running several large applications, which will make products that
- interact with applications like Microsoft Excel and PageMaker more
- attractive to users. Windows programmers can also, of course, write their
- own large, powerful applications. Because the EMS support is transparent to
- the Windows programmer, he or she can use the existing programming
- interface and let the Windows memory manager take advantage of EMS.
-
-
- Figure 1: This memory map shows an MS-DOS application such as Lotus 1-2-3
- using EMS 3.2 expanded memory.
-
- Physical Address Logical Pages of
- Space Expanded Memory
-
- 1024Kb┌─────────────────┐ ┌─────────────────┐
- │ System ROM │ │ L-0 │
- 960Kb├─────────────────┤ ├─────────────────┤
- │ Unused │ │ L-1 │
- ┌──────────┬832Kb├─────────────────┤ ├─────────────────┤
- │EMS 3.2 │ │ P-0 ├────┐ │ L-2 │
- │Page Frame│ ├─────────────────┤ │ ┌─├─────────────────┤
- │made up of│ │ P-1 ├──┐ └│ │ L-3 │
- │four 16Kb │ ├─────────────────┤ │ └─├─────────────────┤
- │physical │ │ P-2 │ │ │ L-4 │
- │pages. │ ├─────────────────┤ │ ├─────────────────┤
- │ │ │ P-3 │ │ │ L-5 │
- └──────────┴786Kb├─────────────────┤ │ ┌─├─────────────────┤
- │ EGA Memory │ └──│ │ L-6 │
- 640Kb├─────────────────┤ └─├─────────────────┤
- │ │ │ L-7 │
- │ │ ├─────────────────┤
- │ MS-DOS │ │ L-8 │
- │ Application │ ├─────────────────┤
- │ Program │ │ L-9 │
- │ │ ├─────────────────┤
- ├─────────────────┤ │ L-10 │
- │ Expanded Memory │ ├─────────────────┤
- │ Manager │ │ L-11 │
- ├─────────────────┤ ├─────────────────┤
- │ MS-DOS │ │ L-12 │
- └─────────────────┘ ├─────────────────┤
- │ L-13 │
- ├─────────────────┤─┐16Kb
- │ L-14 │ │logical
- ├─────────────────┤─┘page
- │ L-15 │
- ├─────────────────┤
- │ ∙ │
- │ ∙ │
- │ ∙ │
- └─────────────────┘
-
-
- Figure 2: This sample memory map shows Write, Pagemaker, and Microsoft
- Excel code and data segments loaded into EMS memory. The Micro-
- soft Excel segments are currently mapped into the physical
- address space of the CPU.
-
- ┌────────────┐ ┌──────────────┐ ┌───────────────────┐
- │ Banked ├───────┤ System ROM │ │ Write │
- └────────────┘ 960Kb └──────────────┘ ├───────────────────┤
- ┌────────────┐ ────────────── │ Write │
- │ 12-16Kb │ ────────────── ├───────────────────┤
- │ physical ├─────── ────────────── │ Write │
- │ pages │ ────────────── ┐ ├───────────────────┤
- └────────────┘ ────────────── ├────┐ │ PageMaker │
- ┌────────────┐ 768Kb ┌─────────────┐┘ │ ├───────────────────┤
- │ Not ├────────┤ EGA │ │ │ PageMaker │
- │ Banked │ │ Memory │ │ ├───────────────────┤
- └────────────┘ 640Kb └─────────────┘ │ │ PageMaker │
- ┌────────────┐ ────────────── │ ┌─├───────────────────┤
- │ 24-16Kb │ ────────────── │ │ │ Microsoft Excel │
- │ physical ├─────── ────────────── │ │ ├───────────────────┤
- │ pages │ ────────────── ┐ └│ │ Microsoft Excel │
- └────────────┘ ────────────── ├──┐ │ ├───────────────────┤
- ─ Bank Line ─ 256Kb ┌─────────────┐┘ │ │ │ Microsoft Excel │
- ┌────────────┐ │ │ │ └─├───────────────────┤
- │ │ │ │ │ │ Write │
- │ │ │ Windows │ │ ├───────────────────┤
- │ │ │ Shareable │ │ │ Write │
- │ │ │ Memory │ │ ├───────────────────┤
- │ │ │ │ │ │ PageMaker │
- │ Not │ │ │ │ ├───────────────────┤
- │ Banked │ ├─────────────┤ │ │ PageMaker │
- │ ├────────│ Windows │ │ ├───────────────────┤
- │ │ ├─────────────┤ │ │ PageMaker │
- │ │ │ Expanded │ │ ┌─├───────────────────┤─┐16Kb
- │ │ │ Memory │ │ │ │ Microsoft Excel │ ├logical
- │ │ │ Manager │ │ │ ├───────────────────┤─┘page
- │ │ │ │ └──│ │ Microsoft Excel │
- │ │ ├─────────────┤ │ ├───────────────────┤
- │ │ │ MS-DOS │ │ │ Microsoft Excel │
- └────────────┘ └─────────────┘ └─└───────────────────┘
- Physical Logical Pages of
- Address Expanded Memory
- Space
-
-
- Figure 3: Expanded Memory is Used for Both Types of Page Frames
-
- Always Above
- below the ┌──the line───┐
- line Small Large
- Frame Frame
-
- Task databases √
- Module (EXE) headers √
- Library data segments √
- Library fixed Code √
- Thunks √
- Library resources √
- Task data segments √ √
- Task Resources √ √
- Task data segments √
- Library discardable Code √
- Dynamically Allocated Memory
- (via GlobalAlloc) √
-
-
- ───────────────────────────────────────────────────────────────────────────
- Glossary of Windows Memory Terms
- ───────────────────────────────────────────────────────────────────────────
-
- Bank line - Also called the EMS swap line, it is the memory address below
- which memory is not banked and above which memory is banked. When Windows is
- running with expanded memory, the nonbanked memory is always available,
- while the banked memory is only available when the task that owns the banks
- is running.
-
- Banked memory - Memory that is made to appear within (mapped into) the
- physical address space of the 8088 by means of a software driver (the
- Expanded Memory Manager) working with an expanded memory card.
-
- Context switch - The changes that Windows must make when the user turns his
- attention from one Windows application to another. For example, suppose both
- WindowsWrite and WindowsPaint are open. If the user has been entering text
- into WindowsWrite and then clicks the mouse in WindowsPaint, the system
- performs a context switch. The only thing the user sees is that the caption
- bar changes color. If the newly active window was covered by another
- overlapping window in Windows 2.0, it is brought to the top.
-
- Discardable memory object - One of the flags that the Windows memory manager
- sets to indicate various characteristics of the memory object when it
- allocates memory. When the Windows memory manager is unable to satisfy
- allocation requests by simply moving memory objects, it will throw away
- discardable memory objects on a least-recently-used basis.
-
- Dynamically allocated memory - The memory that a Windows program directly
- obtains from the Windows memory manager. In this article, the term is
- synonymous with globally allocated memory.
-
- Enhanced Expanded Memory Specification (EEMS) - An extension to the Expanded
- Memory Specification that allows any part of the 8088 address space to be
- banked.
-
- Expanded Memory Specification (EMS) - This specification, also known as LIM
- EMS, defines how a program allocates and uses expanded memory. This consists
- of calls to the expanded memory device driver (see Expanded Memory
- Manager).
-
- Expanded Memory Manager (EMM) - A device driver that controls expanded
- memory. The EMS defines how a program communicates with the EMM to allocate,
- map, and free logical pages of expanded memory.
-
- Extended memory - The memory above 1Mb on a 80286- or 80386-based
- microcomputer. Memory of this type can only be directly addressed when
- running in protected mode, such as in OS/2. Ordinarily, extended memory is
- used for RAM disks and print spoolers. Most memory cards can be configured
- for expanded memory, extended memory, or both.
-
- Handle - A token that is returned when an object is created. Handles
- identify Windows' dynamically allocated memory objects.
-
- Logical page - A segment of expanded memory. EMS memory is allocated in
- logical pages, which are typically 16Kb. In order to use data stored in a
- logical page, EMS must map the logical page into a physical page.
-
- Mapping - The process by which a logical page of expanded memory is made to
- appear in a physical page, that is, within the address space of the 8088, so
- that programs can read and write to the memory.
-
- Old application support - The ability of Windows to execute standard,
- character-based MS-DOS applications, such as Microsoft Word, Lotus 1-2-3, or
- Microrim(R) R:BASE(R) System V.
-
- Page frame - A collection of physical pages into which expanded memory
- logical pages can be made to appear.
-
- Physical page - The absolute physical space in the 8088's 1Mb of address
- space into which EMS logical pages are mapped.
-
- Thunk - A small piece of code that sits in a fixed place in memory to
- intercept the calls to a routine located in a moveable or discardable code
- segment. Thunks are an important part of the dynamic linking mechanism of
- Windows, allowing the memory manager to move or discard code segments with
- relatively little overhead expended towards notifying the prospective
- callers of a routine that the routine is no longer resident. Subsequent
- calls cause the thunk to reload the segment containing the routine. Return
- thunks are created to intercept a return to a discarded code segment. Thunks
- also perform context switches; this is what the thunk created by
- MakeProcInstance does to ensure that a dialog box procedure is using the
- correct data segment.
-
- Window - Another term for an EMS page frame.
-
- Windows memory manager - The part of Windows that loads code and data
- segments into memory. The Windows memory manager attempts to satisfy dynamic
- allocation requests by first moving memory objects; if this is not
- sufficient, it then discards memory objects that are marked as discardable.
- The memory manager in Windows 2.0 has been extended to bank-switch memory
- between Windows applications.
-
- ████████████████████████████████████████████████████████████████████████████
-
- LIM EMS 4.0: A Definition For the Next Generation of Expanded Memory
-
- Marion Hansen and John Driscoll
-
- Back in the bad old days, if your spreadsheet program returned a memory-
- full error, you were out of memory──and out of luck. All that changed in
- 1984, when Lotus Development Corp., Intel Corp., and Microsoft Corp.
- smashed the 640Kb conventional memory barrier with their Expanded Memory
- Specification (EMS). The EMS allowed the creation of another kind of
- memory: expanded memory.
-
- Expanded resides on one or more add-in boards in the computer (Intel's
- Above(TM) Board and AST's Advantage!(TM) are examples). The EMS defines a
- segment of memory, located within the first 1Mb of memory, as the page
- frame. The page frame is a window into expanded memory.
-
- Just after an application program starts executing, it can allocate a
- certain number of 16Kb pages of expanded memory for its own use. By
- mapping pages in and out of the frame, the program can access any area of
- expanded memory it has allocated for itself.
-
- MS-DOS(R) cannot access expanded memory directly; instead, the job of
- handling the extra memory goes to the Expanded Memory Manager (EMM), which
- is defined by the EMS. EMM functions can be added to any program to enable
- it to use expanded memory. (See "Expanded Memory: Writing Programs that
- Break the 640Kb Barrier," MSJ, March 1987, for full details on expanded
- memory and how to write programs that use it.)
-
- To avoid confusion, remember that EMS is the Expanded Memory
- Specification, and that EMM is the Expanded Memory Manager. EMS (the spec)
- defines the EMM (the manager), and the EMM provides the functions that
- application programs need to use expanded memory.
-
- In the fall of 1987 Lotus, Intel, and Microsoft released the latest EMS,
- Version 4.0. This article describes the enhancements to EMS 4.0 and how to
- use them for even greater expanded memory performance.
-
-
- New Feature Set
-
- Intel sent a free EMM 4.0 upgrade to all registered Above Board users.
- Except for faster execution of expanded memory tests at power-on, however,
- users will not see improved performance with their existing programs
- until they have access to new application programs written specifically to
- the EMS 4.0 spec. Even so, certain features of EMS 4.0 have generated
- excitement in the expanded memory world:
-
- ■ With AST's endorsement, EMS 4.0 is now the expanded memory standard
- for the personal computer industry.
-
- ■ The 15 new functions defined by EMS 4.0 provide faster performance
- and more efficient use of memory.
-
- ■ EMS 4.0 supports a full 32Mb of expanded memory; earlier versions
- only supported up to 8Mb.
-
- The OS/2 systems don't run on 8088- or 8086-based computers. EMS 4.0
- extends the useful life of existing MS-DOS-based applications on 8088-,
- 8086-, 80286-, and 80386-based computers.
-
- ■ Many software vendors are already updating their programs (such as
- Lotus(R) 1-2-3(R) Version 3 and Microsoft Windows 2.0) in order to take
- advantage of EMS 4.0's new features.
-
- ■ Software written for earlier versions of the EMS is compatible with
- EMS 4.0.
-
- ■ EMM 4.0 runs on existing hardware; you don't need new expanded
- memory boards to install EMM 4.0. However, not all expanded memory
- boards currently support every enhancement to EMM 4.0. For example,
- the AST RAMpage!(R) board and the Intel Above Board 2 support placing
- the page frame in memory below 640Kb, while the Intel Above Board
- 286 currently does not.
-
-
- EMS 4.0 Enhancements
-
- The enhancements to EMS 4.0 improve performance and memory efficiency and
- make it easier to write programs that use expanded memory.
-
- ■ With EMM 4.0, one calls maps in all logical pages that can be
- physically mapped to one page frame; earlier versions required each
- page to be mapped separately.
-
- ■ New functions allow application programs to dynamically increase
- and decrease the amount of expanded memory allocated to them. With
- each program using just the amount of expanded memory it needs,
- multiple programs can share expanded memory more efficiently.
-
- ■ Now you can name handles-the values that the EMM assigns and uses to
- identify a block of memory requested by an application program-so
- that data and code can be shared by application programs.
-
- ■ EMS 4.0 adds special functions for operating systems (such as MS-
- DOS) and environments (such as Windows and Desqview) to allow
- programs to isolate themselves from all other software in the
- system, thus providing a safe environment for programs sharing
- expanded memory.
-
- ■ New EMS functions directly support executing code in expanded memory.
- Earlier versions supported the execution of code in expanded
- memory, but implementation was cumbersome. For example, before EMS
- 4.0, terminate-and-stay-resident (TSR) programs required commands to
- explicitly map the new context into expanded memory when the TSR
- program was called up and to restore the previous context when the
- TSR returned control to the application program. Now the job of
- mapping and restoring contexts can be handled directly with EMS 4.0
- functions, which allows for easier and more efficient execution of
- code.
-
- ■ Earlier EMS versions put the page frame in an unused 64Kb block of
- memory between 640Kb and 1Mb. EMS 4.0 supports the page frame
- anywhere in the first 1Mb of memory. Hardware may limit the
- location of the page frame-in an 80286-based computer such as an IBM(R)
- PC/AT(R), for example, the page frame can reside only between 256Kb
- and 1Mb. For an 80386-based computer or an IBM PS/2(TM) computer
- with the Micro Channel(TM), on the other hand, the page frame can be
- anywhere in the first 1Mb of memory.
-
- ■ Before EMS 4.0, the page frame held four pages. Now you can define a
- page frame of up to eight pages in memory above 640Kb. The size of
- the page frame in memory below 640Kb is limited only by the amount of
- available memory. (In some implementations of the EMM, you can
- use all 640Kb for the page frame by putting the EMM itself into
- expanded memory.)
-
- ■ Although the standard size of expanded memory pages is 16Kb, some
- expanded memory boards use smaller pages. EMS 4.0 supports both
- 16Kb pages and smaller-sized pages.
-
-
- The Page Frame
-
- Although EMM 4.0 supports locating a page frame in any available memory
- between 0Kb and 1Mb, how you plan to use the page frame determines whether
- you'll place it above or below 640Kb. For application programs, continue
- to locate the page frame above 640Kb because existing applications may
- not work when the page frame is located below 640Kb. Locate page frames
- used by most operating systems and environments in memory below 640Kb.
- (An exception to this is the Windows/386 EMM, which cannot use a page
- frame located below 640Kb.) In any case, you cannot create a page frame in
- memory below 640Kb unless you first create a page frame in memory above
- 640Kb.
-
-
- New EMM Parameters
-
- The LIM EMM 4.0 device driver has new parameters, one of which is of
- special interest to users who are concerned with speed and efficiency.
-
- The handle count parameter lets you tell the EMM to support as many
- handles as a particular application program needs. (A handle is a value
- that the EMM assigns and uses to identify a block of memory requested by
- an application program.) The EMM allocates memory based on the number of
- handles requested by the application. By specifying a small handle count,
- you can save conventional memory and allow the EMM to run faster.
-
- The EMM 4.0 handle count default is 64 handles; programs written for
- earlier versions use a maximum of 32 handles. If you are using older
- programs with EMM 4.0, consider changing the EMM handle count to 32 for
- faster execution. The maximum number of handles is 254.
-
-
- The EMM Functions
-
- The EMM functions provide the tools application programs need to use
- expanded memory. EMS 4.0 doubled the number of EMM functions. Figure 1
- lists all 30 functions; the new functions are shaded.
-
- Functions 16 through 30 are new to EMM 4.0. This section describes each
- new function. Functions 26, 28, and 30 are for use by operating systems
- (such as MS-DOS) and environments (Windows and Desqview, for example);
- the rest are for application programs.
-
-
- Program Functions
-
- Get/Set Partial Page Map (Function 16) handles situations Functions 8, 9,
- and 15 cannot handle. Functions 8 and 9, respectively, handles only four-
- page page frames and can save and restore expanded memory pages only when
- the page frame is located between 640Kb and 1Mb. Function 15 saves and
- restores all the pages (no matter how many pages are in the page frame),
- even pages located below 640Kb. Function 16 also saves and restores all
- pages anywhere between 0 and 1Mb, but, unlike Function 15, it lets you
- choose the pages you want to save or restore.
-
- Function 16 has three subfunctions. The Get Partial Page Map subfunction
- saves a partial mapping context for specific mappable memory regions in a
- system. This subfunction can be faster than Function 15 because it saves
- only a subset of the entire mapping context and uses much less memory for
- the save area. You can use the Get Partial Page Map subfunction with or
- without a handle, while Function 8 requires a handle.
-
- The Set Partial Page Map subfunction restores a partial mapping context
- for specific mappable memory regions in a system. Like Get Partial Page
- Map, this subfunction uses far less memory for the save area than Function
- 15, and you can use it with or without a handle.
-
- The Get Size of Partial Page Map Save Array subfunction returns the
- storage requirements for the array passed by the Get/Set Partial Page Map
- subfunctions. Use this subfunction before the other two
- subfunctions.
-
- Map/Unmap Multiple Handle Pages (Function 17) maps or unmaps (in one
- invocation) logical pages into as many physical pages as the system
- supports. This means less overhead than mapping pages one at a time (as
- required by EMS versions before 4.0). Use this function instead of
- Function 5 for application programs that do a lot of page mapping.
-
- Reallocate Pages (Function 18) allows an application program to
- dynamically increase or decrease the number of logical pages allocated to
- an EMM handle, so programs can share expanded memory more efficiently.
- Function 18 does not determine the number of pages a program needs;
- rather, the program itself must specify the number of pages to allocate
- or deallocate (depending on its needs at any one time).
-
- Get/Set Handle Attribute (Function 19) defines a handle as volatile or
- nonvolatile. If it is nonvolatile, the handle, its name (if it has one),
- and the contents of the pages allocated to it are maintained after a warm
- boot. For example, this function saves the contents of a RAM disk in
- expanded memory after a warm boot. Function 19 is not supported by most
- hardware because it disables memory refresh signals for a long period
- of time.
-
- Function 19 has three subfunctions. The Get Handle Attribute
- subfunction returns the attribute (volatile or non-volatile) associated
- with a handle. The Set Handle Attribute subfunction changes the
- attribute associated with a handle. Before using Function 19's other two
- subfunctions, you can use the Get Attribute Capability subfunction to
- determine whether the EMM supports the non-volatile attribute.
-
- By naming the handle associated with a block of memory, Get/Set Handle
- Name (Function 20) lets application programs share the same area in
- expanded memory associated with a handle. Assigning a name to a handle
- also protects the memory specified by the handle because only a program
- that knows the handle name can get that handle and use the memory assigned
- to it. Function 20 has two subfunctions. The Get Handle Name subfunction
- gets the eight-character name assigned to a handle. The Set Handle Name
- subfunction assigns an eight-character name to a handle.
-
- Get Handle Directory (Function 21) has three subfunctions. The Get
- Handle Directory subfunction returns an array which contains all active
- handles and their associated names (if any).
-
- The Search for Named Handle subfunction searches the handle name directory
- for the specified handle name. If it finds the name, the subfunction then
- returns the handle number associated with the name. An application
- program is able to use this subfunction to determine if a shared handle
- exists.
-
- The Get Total Handles subfunction returns the total number of handles
- that the EMM supports. (Different versions of EMM support different
- numbers of handles. EMM 4.0 supports up to 254 handles; earlier
- versions support just 32).
-
- Functions 22 and 23 make executing code in expanded memory easier and more
- efficient. Alter Page Map and Jump (Function 22) changes the memory
- mapping context and transfers control to the specified address. The
- original memory mapping context is lost. This function is analogous to
- the FAR JUMP in the 8086 family architecture.
-
- Alter Page Map and Call (Function 23) contains two subfunctions. The Alter
- Page Map and Call subfunction saves the current memory mapping context,
- alters the specified memory mapping context, transfers control to the
- specified address, and restores the state of the specified mapping
- context after the return. This subfunction is analogous to the FAR CALL
- in the 8086 family architecture.
-
- The Alter Page Map and Call subfunction pushes information onto the stack.
- The Get Page Map Stack Space Size subfunction returns the number of
- bytes of stack space the Alter Page Map and Call subfunction needs to do
- this. Use the information provided by Get Page Map Stack Space Size to
- alter the stack size before using either Function 22 or the Alter Page Map
- and Call subfunction.
-
- Move/Exchange Memory Region (Function 24) lets you move large amounts of
- memory without mapping and unmapping pages. You can move or exchange up
- to 84 pages without mapping any logical pages. Function 24 has two
- subfunctions. The Move Memory Region subfunction copies and the Exchange
- Memory Region subfunction exchanges: conventional memory to
- conventional memory, conventional memory to expanded memory, expanded
- memory to conventional memory, and expanded memory to expanded memory.
- The subfunctions maintain the current mapping context, so the program
- does not have to save and restore it.
-
- Get Mappable Physical Address Array (Function 25) tells an application
- what physical memory is mappable. Function 2 (get page frame address) gives
- the segment address of the first four pages in the page frame. Use Function
- 25 to find the amount of mappable physical memory for page frames larger
- than four pages. Function 25 has two subfunctions. The Get Mappable Physical
- Address Array subfunction returns an array containing the segment address
- and physical page number for each mappable physical page in a system. The
- array is a cross reference between physical page numbers and the actual
- segment addresses for each mappable page in the system. The array is sorted
- by segment address in ascending order, but the physical page numbers
- associated with the segment address are not necessarily in ascending order.
-
- The Get Mappable Physical Address Array Entries subfunction gets the
- number of entries needed by the array returned by the Get Mappable
- Physical Address Array subfunction. Before using the Get Mappable
- Physical Address Array Entries subfunction, use the Get Mappable
- Physical Address Array subfunction to determine how much memory to
- allocate for storing the physical address array.
-
- Allocate Raw Pages (Function 27) with two subfunctions allocates the
- number of raw pages requested by the operating system or environment
- and assigns a unique EMM handle to these pages. (Some expanded memory
- boards have pages of less than the standard 16Kb. These non-standard sized
- memory pages are called raw pages.) Handles assigned to raw pages are
- called raw handles. Function 4 (Allocate Pages) allocates only 16Kb
- pages; it does not support raw pages.
-
- Programs whose page frames are in conventional memory must use Prepare
- Expanded Memory Hardware for Warm Boot (Function 29) when they detect
- Ctrl-Alt-Del. This function prepares the expanded memory hardware for an
- impending warm boot.
-
-
- Environment Functions
-
- The following functions are for inclusion in operating system and
- environment programs so the operating system/environment knows the kind
- of hardware support available for expanded memory. Never use Function 26,
- 28, or 30 in application programs.
-
- Get Expanded Memory Hardware Information (Function 26) is only for use
- by operating systems (such as DOS) and environments (such as Windows
- 386 and Desqview). The operating system/environment can disable Function
- 26 at any time. The function has two subfunctions. The Get Hardware
- Configuration Array subfunction returns an array containing expanded
- memory hardware configuration information for use by operating systems
- and environments. The operating system/environment uses this information
- to determine the hardware support for expanded memory, including raw page
- size, alternate register sets available, context save area size, and
- register sets for DMA channels.
-
- Some expanded memory boards have pages of less than the standard 16Kb,
- called raw pages. Function 3 (Get Unallocated Page Count) returns only
- the number of 16Kb pages. The Get Unallocated Raw Page Count subfunction
- returns the number of unallocated raw mappable pages and the total number
- of raw mappable pages in expanded memory to the operating
- system/environment.
-
- Alternate Map Register Set (Function 28) is for use by operating systems
- and environments only. It has nine subfunctions.
-
- The Get Alternate Map Register Set subfunction responds in one of two
- ways, depending on the setting of the map register set which is active
- when the function is invoked. If the map register set is equal to zero, a
- pointer to a context save area is returned. If the map register set is
- greater than zero, the number of the alternate map register set is
- returned.
-
- The Set Alternate Map Register Set subfunction responds in one of two
- ways, depending on the map register set specified. If the alternate map
- register set equals zero, map register set zero is activated, and the
- contents of the map register context save area is copied into register
- set zero on each expanded memory board in the system. If the alternate
- map register set specified is not zero, the alternate map register set
- specified is activated. The restore area, which the operating system is
- pointing to, is not used.
-
- The Get Alternate Map Save Array Size subfunction returns the storage
- requirements for the map register context save area that is referenced by
- the other subfunctions.
-
- If an alternate map register is available, the Allocate Alternate Map
- Register Set subfunction gets its number and copies the currently active
- alternate map register set's contents into the newly allocated alternate
- map register set's mapping registers. This does not change the alternate
- map register set in use but prepares the new alternate map register set
- for a subsequent Set Alternate Map Register Set subfunction. Operating
- systems can use this subfunction to quickly switch mapping contexts.
-
- The Deallocate Alternate Map Register Set subfunction returns the
- alternate map register set to the memory manager. The memory manager can
- then reallocate the alternate map register set. This subfunction also
- makes the mapping context of the specified alternate map register
- unavailable for reading or writing, thus protecting the pages previously
- mapped in an alternate map register set by making them inaccessible. The
- current alternate map register set cannot be deallocated, so memory
- currently mapped into conventional and expanded memory is inaccessible.
-
- The four DMA subfunctions let operating systems/environments use DMA
- register sets. (Hardware that supports these subfunctions is not yet
- available.) If a DMA register set is available, the Allocate DMA Register
- Set subfunction gets the current number of a DMA register set for an
- operating system/environment. This subfunction is useful with
- multitasking operating systems, in which you would like to switch to
- another task when one task is waiting for DMA to complete. The Deallocate
- DMA Register Set subfunction deallocates the specified DMA register set.
-
- The Enable DMA on Alternate Map Register Set subfunction allows DMA
- accesses on a specific DMA channel to be associated with a specific
- alternate map register set. This function is useful in a multitasking
- operating system, where you would like to switch to another task until
- the first task's DMA operation completes. The Disable DMA on Alternate
- Map Register Set subfunction disables DMA accesses for all DMA channels
- which were associated with a specific alternate map register set.
-
- Enable/Disable OS/E Function Set (Function 30) is only used by operating
- systems and environments. It includes three subfunctions which are
- Enable OS/E Function Set subfunction that will enable Function 26 (Get
- Expanded Memory Hardware Information), Function 28 (Alternate Map
- Register Sets), and also Function 30 (Enable/Disable Operating System
- Functions).
-
- The Disable OS/E Function Set subfunction disables Functions 26, 28, and
- 30.
-
- The Return Access Key subfunction lets the operating system/environment
- return the access key to the EMM. Returning the access key to the EMM
- enables access to the operating system/environment function set.
-
-
- Example Programs
-
- The following two examples (written in Microsoft(R) C, Version 5.0, and
- Microsoft MASM, Version 5.0) illustrate how functions new to EMS 4.0 can
- be implemented. The first example shows how expanded memory can be shared
- between two application programs. The first program (SAVSCR.EXE) saves
- the current screen, initializes a blank screen in expanded memory, and
- then exits (see Figure 2). The second program (SWPSCR.EXE) saves its
- current screen, displays the blank screen and the first program's current
- screen, and then restores its own current screen.
-
- Before it can use expanded memory, SAVSCR.EXE must first see if the EMM is
- present (by getting the device name from the device driver header).
- Because SAVSCR.EXE uses 4.0 functions, it also checks the EMM version
- (Function 7), and exits if EMM is earlier than version 4.0 (see Figure 3).
-
- SAVSCR.EXE next determines the number of unallocated pages in expanded
- memory (Function 3) and exits if there are fewer pages than it needs.
- Then the program allocates the expanded memory pages it needs and gets an
- EMM handle (Function 4), assigning the unique name SHARED to the handle
- (Function 20). This unique handle name will be used later by the second
- application program to find the handle so it can use the same expanded
- memory (see Figure 4).
-
- Next, SAVSCR.EXE uses the Map/Unmap Multiple Handle function (Function 17)
- to map the needed pages into the page frame. This is done with one
- function call; earlier versions of the EMM require one call for each page
- (see Figure 5).
-
- Then the SAVSCR.EXE program copies the current screen to logical page 0
- and physical page 0, using the Move Memory Region subfunction (Function
- 24). Although this particular move does not span any more than one
- expanded memory page, the same function call can transfer up to 1Mb of
- memory without mapping in any logical pages of expanded memory (see
- Figure 6).
-
- Finally, SAVSCR.EXE unmaps all of the pages using the Map/Unmap Multiple
- Handle Pages function (Function 17) and then exits without returning the
- handle to the EMM (see Figure 5).
-
- Expanded memory is now protected. The second program, SWPSCR.EXE, can find
- this same expanded memory by using the Search for Named Handle
- subfunction.
-
- First, SWPSCR.EXE verifies that the EMM is present and is version 4.0 or
- greater, as the first program did. However, unlike the SAVSCR.EXE
- program, SWPSCR.EXE does not need to allocate pages because it can use the
- same handle named SHARED that SAVSCR.EXE received.
-
- SWPSCR.EXE uses the Search for Named Handle subfunction (Function 21) to
- determine if the handle named SHARED is present. If the first program
- (SAVSCR.EXE), has not been executed already, SWPSCR.EXE will exit,
- because it could not find the handle named SHARED.
-
- Finally, SWPSCR.EXE uses the Exchange Memory Region subfunction (Function
- 24) to: swap its current screen with the logical page that has the blank
- screen; swap the blank screen with the logical page that has the
- original screen; and then restore its own current screen. (Because the
- Exchange Memory Region subfunction handles all mapping, none of the
- logical pages have to be mapped before this call.)
-
- SWPSCR.EXE unmaps all expanded memory pages be-fore exiting (Function 17)
- as seen in Figure 7.
-
- SWPSCR.EXE illustrates how EMM 4.0 has simplified executing code in
- expanded memory. (For an idea of just how much EMM 4.0 has accomplished
- in this area, refer to the KERNEL module in "Expanded Memory: Writing
- Programs That Break the 640Kb Barrier," MSJ, March 1987.)
-
- As in the first example, MAPCALL.EXE (see Figure 8) checks for the
- version of EMM and allocates expanded memory. Then it uses the MS-DOS
- Load Overlay function to load two modules (MODULE1.EXE and MODULE2.EXE)
- into expanded memory (see Figure 9).
-
- Next, MAPCALL.EXE uses the Alter Page Map and Call subfunction (Function
- 23) to execute MODULE1.EXE (see Figure 10).
-
- MODULE1.EXE in turn uses the Search for Named Handle subfunction to
- determine which handle to use and uses the Alter Map Page and Call
- subfunction to execute MODULE2.EXE (see Figure 11).
-
- MODULE2.EXE prints a message to the screen and does a far return. The far
- return causes the EMM to restore the map-ping specified in the old map
- page definition and return control to MODULE1.EXE (see Figure 12).
-
- MODULE1.EXE then does a far return, which causes the EMM to return control
- to MAPCALL.EXE and to restore the original mapping context. MAPCALL.EXE
- then releases its handle and exits to MS-DOS.
-
-
- To Get EMS 4.0
-
- If you are interested in developing application programs that use
- expanded memory, call Intel for a free copy of the Lotus/Intel/Microsoft
- Expanded Memory Specification. From the United States and Canada, call
- (800) 538-3373. Outside the United States and Canada, call (503) 629-7354.
-
-
- Figure 1: EMM 4.0 FUNCTIONS
-
- ╓┌──────────────────────────┌────┌───────────────────────────────────────────╖
- Function Name No. Description
-
- GET STATUS 1 Returns a status code to tell you whether
- Function Name No. Description
- GET STATUS 1 Returns a status code to tell you whether
- the EMM is present and the hardware/software
- is working correctly.
-
- GET PAGE FRAME ADDRESS 2 Gives the program the location of the page
- frame.
-
- GET UNALLOCATED PAGE 3 Tells the program the totalCOUNT number of
- pages in expanded memory and the number of
- unallocated pages.
-
- ALLOCATE PAGES 4 Allocates the number of expanded memory
- pages requested by the program; assigns a
- unique EMM handle to the set of pages
- allocated.
-
- MAP HANDLE PAGE 5 Maps the specified logical page in expanded
- memory to the specified physical page within
- the page frame.
-
- Function Name No. Description
- DEALLOCATE PAGES 6 Deallocates the pages currently allocated to
- an EMM handle.
-
- GET EMM VERSION 7 Returns the version number of the EMM
- software.
-
- SAVE PAGE MAP 8 Saves the contents of the page mapping
- registers of all expanded memory boards.
-
- RESTORE PAGE MAP 9 Restores the contents of the page mapping
- registers.
-
- 10 Reserved.
-
- 11 Reserved.
-
- GET EMM HANDLE COUNT 12 Returns the number of active EMM handles.
-
- GET EMM HANDLE PAGES 13 Returns the number of pages allocated to a
- Function Name No. Description
- GET EMM HANDLE PAGES 13 Returns the number of pages allocated to a
- specific EMM handle.
-
- GET ALL EMM HANDLE 14 Returns the active EMM handles PAGES and the
- number of pages allocated to each one.
-
- GET/SET PAGE MAP 15 Saves and restores the mapping context of
- the active EMM handle.
-
- GET/SET PARTIAL PAGE MAP 16 Saves a partial mapping context for specific
- mappable memory regions in a system.
-
- MAP/UNMAP MULTIPLE 17 Maps/unmaps (in a single HANDLE PAGES
- invocation) logical pages into as many
- physical pages as the system supports.
-
- REALLOCATE PAGES 18 Increases or decreases the amount of
- expanded memory allocated to a handle.
-
- GET/SET HANDLE 19 Lets an application determine and ATTRIBUTE
- Function Name No. Description
- GET/SET HANDLE 19 Lets an application determine and ATTRIBUTE
- set a handle as volatile or non-volatile.
-
- GET/SET HANDLE NAME 20 Gets the eight-character name currently
- assigned to a handle assigns an eight-
- character name to a handle.
-
- GET HANDLE DIRECTORY 21 Returns information about active handles and
- the names assigned to each.
-
- ALTER PAGE MAP AND JUMP 22 Alters the memory mapping context and
- transfers control to the specified address.
-
- ALTER PAGE MAP AND CALL 23 Alters the specified mapping context and
- transfers control to the specified address.
- A return can then restore the context and
- return control to the caller.
-
- MOVE/EXCHANGE MEMORY 24 Copies or exchanges a region ofREGION memory
- from conventional to conventional memory,
- Function Name No. Description
- from conventional to conventional memory,
- conventional to expanded memory, expanded to
- conventional memory, or expanded to expanded
- memory.
-
- GET MAPPABLE PHYSICAL 25 Returns an array with the segment ADDRESS
- ARRAYaddress and physical page number for
- each mappable physicalpage in a system.
-
- GET EXPANDED MEMORY 26 Returns an array containing the HARDWARE
- INFORMATION hardware capabilities of the
- expanded memory system.
-
- ALLOCATE RAW PAGES 27 Allocates the number of non-standard size
- pages that the operating system requests and
- assigns a unique EMM handle to these pages.
-
- ALTERNATE MAP REGISTER 28 Lets an application program SET simulate
- alternate sets of hardware mapping
- registers.
- Function Name No. Description
- registers.
-
- PREPARE EXPANDED 29 Prepares the expanded memory MEMORY HARDWARE
- FOR hardware for an impending WARM BOOT warm
- boot.
-
- ENABLE/DISABLE OS/E 30 Enables and disables EMM Functions designed
- for use by operating systems and
- environments (Functions 26, 28, and 30).
-
-
- Figure 2: SAVSCR Main Program
-
- #include <c:\msc\include\dos.h>
- #include <c:\msc\include\memory.h>
- #include <c:\msc\include\stdio.h>
- #include <c:\emm\demo\emm.h>
-
- /* set up size and base of video ram */
- #define VIDEO_RAM_SIZE 4000
- #define VIDEO_RAM_BASE 0XB8000000
- union REGS inregs, outregs;
- struct SREGS segregs ;
-
- char far *video_ram_ptr = {VIDEO_RAM_BASE); /* video start address (CGA)*/
- unsigned long int video_ram_size =; /* bytes in video ram */
- unsigned int emm_handle ; /* our emm handle */
- char emm_device_name[] = "EMMXXXX0"; /* Device Name of EMM */
- char emm_handle_name[8] ="shared" ; /* name for handle to be shared
- char far *emm_ptr; /* pointer to page frame */
- char far *(*page_ptr) ; /* pointer to page in the frame *
- int pages_needed = 4;
- struct log_phys {
- int log_page_number;
- int phys_page_number;
- } current_pages [4] ;
- struct log_phys far *map_unmap_ptr ;
- int result ; /* result passed back from function calls */
-
- main ()
- {
- check_emm_version_number(); /*Check for EMM > 4.0 */
-
- result = get_expanded_memory(&emm_ptr, pages_needed, &emm_handle,
- emm_handle_name);
- if (result != 0) exit(1); /* exit if error */
-
- result = map_unmap_multiple_pages (current_pages, /* Map in */
- emm_handle, 1); /* pages */
-
- move_exchg_to_expanded(MOVE_MEMORY_REGION, /* Copy video screen to */
- video_ram_ptr, emm_handle, 0, video_ram_size); /* logical page 0 */
-
- /* make a null video screen at logical page 1 */
- page_ptr = (emm_ptr + 0x4000); /* make null screen at */
- memset (page_ptr, 0, VIDEO_RAM_SIZE); /* at logical page 1 */
-
- /* Unmap all pages so they are protected */
- result = map_unmap_multiple_pages (current_pages, emm_handle, 0);
-
- }
-
-
- Figure 3: This function checks to see if the EMM is present and if the
- version number is >= 4.0. It uses EMM function 7, GET. VERSION
- NUMBER.
-
- Using EMM Function 7 GET.VERSION NUMBER
-
- int check_emm_version_number()
- {
- char *emm_device_name_ptr ;
-
- inregs.h.ah = 0x35; /* Use the DOS get interrupt function (0x35) to */
- inregs.h.al =EMM_INT;/* get the pointer at interrupt vector 0x67, */
- intdosx(&inregs, &outregs, &segregs);/* and check for device name. */
- emm_device_name_ptr = (segregs.es * 65536) + 10;
- if (memcmp(emm_device_name, emm_device_name_ptr,8) !=0)
- {
- printf("Expanded memory manager not present\n");
- exit(1);
- }
- inregs.h.ah = GET_VERSION ; /* set function code and check for */
- int86(EMM_INT,&inregs,&outregs);/* version >= 4.0 */
- if (outregs.h.ah != 0) exit(1);
- if ((outregs.h.ah == 0) & (outregs.h.al < 0x40))
- {
- printf("Expanded memory manager does not support LIM 4.0");
- exit(1) ;
- }
- }
-
-
- Figure 4: This function gets the amount of expanded memory requested,
- returns a pointer to the page frame, and assigns the name to the
- handle.
-
- Obtaining the Requested Amount of Memory
-
- int get_expanded_memory(emm_ptr_ptr, pages, emm_handle_ptr, name)
-
- char *(*emm_ptr_ptr); /* Pointer to expanded memory page frame */
- int pages; /* Number of pages to allocate */
- unsigned int *emm_handle_ptr; /* Pointer to emm handle */
- char *name;
-
- {
- inregs.h.ah = GET_UNALLOCATED_PAGE_COUNT ; /* Check to see if there */
- int86(EMM_INT, &inregs, &outregs); /* enough unallocated pages left.*/
- if (outregs.h.ah != 0) return(1);
- if (outregs.x.bx < pages) return(2);
-
- inregs.h.ah = ALLOCATE_PAGES ; /* Get a handle and allocate */
- inregs.x.bx = pages; /* the requested pages.*/
- int86(EMM_INT, &inregs, &outregs);
- if (outregs.h.ah != 0) return(3);
- *emm_handle_ptr = outregs.x.dx ;
-
- inregs.h.ah = GET_FRAME_ADDRESS; /* Get page frame segment address */
- int86(EMM_INT, &inregs, &outregs); /* and make it a pointer. */
- if (outregs.h.ah != 0) return(4);
- *emm_ptr_ptr = (unsigned long int) (outregs.x.bx *65536);
-
- inregs.x.ax = SET_HANDLE_NAME ; /* assign name to handle */
- inregs.x.dx = *emm_handle_ptr ;
- inregs.x.si = FP_OFF(name) ;
- segregs.ds = FP_SEG(name);
- int86x(EMM_INT, &inregs, &outregs, &segregs);
- if (outregs.h.ah != 0) return(5);
-
- return(0);
-
- }
-
-
- Figure 5: Implementing the MAP/UNMAP MULTIPLE PAGES EMM Function
-
-
- int map_unmap_multiple_pages (log_phys_pages,handle,map_unmap)
-
- struct log_phys *log_phys_pages ; /* Pointer to log_phys struct */
- unsigned int handle; /* Handle to map or unmap */
- unsigned int map_unmap; /* 0 = map, 1 = unmap */
- {
- int i ;
- struct log_phys *temp_ptr;
-
- temp_ptr = log_phys_pages;
-
- for (i=0 ; i<=3; i++)
- {
- /* Setup the structure to Map or unmap the logical pages 0 to 3 */
- log_phys_pages->phys_page_number = i;
- if (map_unmap == 1)
- log_phys_pages->log_page_number = i;
- else
- log_phys_pages->log_page_number = 0xFFFF ;
-
- log_phys_pages++ ;
-
- }
-
- inregs.x.ax = MAP_UNMAP_MULTIPLE_PAGES ;
- inregs.x.dx = handle;
- inregs.x.cx = 4;
- inregs.x.si = FP_OFF(temp_ptr);
- segregs.ds = FP_SEG(temp_ptr);
- int86x(EMM_INT,&inregs,&outregs,&segregs);
- if (outregs.h.ah != 0) return(1);
- return(0);
- }
-
-
- Figure 6: This function implements the MOVE or EXCHANGE MEMORY REGION
- function to move or exchange conventional memory with expanded
- memory pages.
-
- Implementing the MOVE or EXCHANGE MEMORY REGION Function
-
- int move_exchg_to_expanded(function_number, conv_buffer, handle,
- page, length)
- unsigned int function_number ; /* Move or Exchange*/
- char far *conv_buffer ; /* conventional memory with */
- int handle; /* EMM memory associated with this handle*/
- int page ; /* at this physical page */
- unsigned long int length; /* and for this many bytes */
-
- {
- #pragma pack(1) /* Make sure the following structure
- is byte aligned */
- struct move_exchg
- {
- unsigned long int region_length;
- char source_type ;
- unsigned int source_handle ;
- unsigned int source_offset ;
- unsigned int source_seg_page;
- char dest_type;
- unsigned int dest_handle;
- unsigned int dest_offset;
- unsigned int dest_seg_page;
- } move_exchg_struct;
- struct move_exchg *move_exchg_ptr;
-
- move_exchg_struct.region_length = length ;
- move_exchg_struct.source_type = 0;
- move_exchg_struct.source_handle = 0;
- move_exchg_struct.source_offset = FP_OFF(conv_buffer);
- move_exchg_struct.source_seg_page = FP_SEG(conv_buffer);
- move_exchg_struct.dest_type = 1;
- move_exchg_struct.dest_handle = handle;
- move_exchg_struct.dest_offset = 0 ;
- move_exchg_struct.dest_seg_page = page;
-
- inregs.x.ax = function_number;
- move_exchg_ptr = &move_exchg_struct;
- inregs.x.si = FP_OFF(move_exchg_ptr);
- segregs.ds = FP_SEG(move_exchg_ptr);
- int86x(EMM_INT, &inregs, &outregs, &segregs);
- if (outregs.h.ah != 0) exit(1);
-
- return(outregs.x.ax) ;
-
- }
-
-
- Figure 7: The Main Program for SWPSCR
-
- #include <c:\msc\include\dos.h>
- #include <c:\msc\include\memory.h>
- #include <c:\msc\include\stdio.h>
- #include <c:\emm\demo\emm.h>
- #define VIDEO_RAM_SIZE 4000
- #define VIDEO_RAM_BASE 0XB8000000
-
- union REGS inregs, outregs;
- struct SREGS segregs ;
-
- char far *video_ram_ptr = {VIDEO_RAM_BASE}; /* video start address (CGA)*/
- unsigned long int video_ram_size ={4000}; /* bytes in video ram */
- unsigned int emm_handle ; /* emm handle */
- char emm_device_name[] = "EMMXXXX0"; /* Device Name of EMM */
- char emm_handle_name[8] ="shared" ; /* name for handle to be shared */
- char far *(*expanded_memory_ptr) ; /* pointer to page frame */
- char far *(*page_ptr) ; /* pointer to page in the frame */
- long target_time,current_time ;
- struct log_phys { /* structure to hold the mapping of logical */
- int log_page_number; /* pages to physical pages */
- int phys_page_number;
- } current_pages [4] ;
-
- main ()
- {
-
- check_emm_version_number();
-
- search_for_handle(emm_handle_name, &emm_handle);
-
- /* Exchange screens*/
- move_exchg_to_expanded(EXCHANGE_MEMORY_REGION,video_ram_ptr,
- emm_handle, 1, video_ram_size);
-
- time(¤t_time); /* Delay so user can see screen changes */
- target_time = current_time + 3;
- while (current_time < target_time) time(¤t_time);
-
- /* Display Screen */
- move_exchg_to_expanded(EXCHANGE_MEMORY_REGION, video_ram_ptr,
- emm_handle, 0, video_ram_size);
- time(¤t_time); /* Delay so user can see screen change */
- target_time = current_time + 3;
- while (current_time < target_time) time(¤t_time);
-
- /* Restore original screen */
- move_exchg_to_expanded(EXCHANGE_MEMORY_REGION,video_ram_ptr,
- emm_handle, 1, video_ram_size);
-
- result = map_unmap_multiple_pages (current_pages, emm_handle, 0);
-
- exit(0) ;
- }
-
-
- Figure 8: The Main Program for MAPCALL
-
- #include <c:\msc\include\dos.h>
- #include <c:\msc\include\memory.h>
- #include <c:\msc\include\stdio.h>
- #include <c:\emm\demo\emm.h>
-
- union REGS inregs, outregs;
- struct SREGS segregs ;
-
- unsigned int emm_handle ;
- char emm_device_name[] = "EMMXXXX0"; /* Device Name of EMM *
- char far emm_handle_name[8] ="mapcall"; /* Name for handle */
- char far mod1_name[] ="c:\\emm\\demo\\module1.exe\0"; /* Name of modules */
- char far mod2_name[] ="c:\\emm\\demo\\module2.exe\0"; /* to be loaded */
- char far *emm_ptr ;
- char far *(*page_ptr) ;
- int pages_needed = 16 ;
- struct log_phys {
- int log_page_number;
- int phys_page_number;
- } ;
- struct log_phys far current_pages[4];
- struct log_phys far map_call_pages ;
- int result;
-
- main ()
- {
-
-
- check_emm_version_number();
-
- result = get_expanded_memory(&emm_ptr, pages_needed, &emm_handle,
- emm_handle_name);
- if (result != 0) exit(1);
-
- /* Map in pages */
- result = map_unmap_multiple_pages (current_pages,
- emm_handle, 1);
-
- *page_ptr = emm_ptr; /* Load Module 1 into logical page 0 */
- load_overlay(mod1_name, page_ptr, 0); /* at physical page 0 */
-
- /*Load Module 2 into logical page 1 at physical page 1 */
- load_overlay(mod2_name, page_ptr, 1);
-
- /* Unmap all pages */
- result = map_unmap_multiple_pages (current_pages,
- emm_handle, 0);
-
- /* Map and call to module in page 0 and physical page 1 */
- map_call_pages.log_page_number = 0;
-
- map_call_pages.phys_page_number = 0;
-
-
- map_and_call(&map_call_pages, 1, ¤t_pages, 0, emm_handle);
-
- inregs.h.ah = DEALLOCATE_PAGES ; /* Release handle before
- exiting */
- inregs.x.dx = emm_handle ;
- int86(EMM_INT, &inregs, &outregs);
-
- exit(0);
- }
-
-
- Figure 9: Implementing the ALTER PAGE MAP AND CALL EMM Function
-
- int map_and_call(new_map_ptr, new_length, old_map_ptr, old_length,
- handle)
- struct log_phys *new_map_ptr;
- char new_length;
- struct log_phys *old_map_ptr;
- char old_length;
- unsigned int handle;
- {
- # pragma pack(1) /* Make sure structure is byte aligned */
- struct map_call_struct {
- unsigned int offset_target_address ;
- unsigned int seg_target_address ;
- char new_page_map_length ;
- unsigned int offset_new_page_map;
- unsigned int seg_new_page_map ;
- char old_page_map_length ;
- unsigned int offset_old_page_map;
- unsigned int seg_old_page_map ;
- } map_call;
- struct map_call_struct *map_call_ptr;
-
- map_call_ptr = &map_call ;
- map_call.offset_target_address = 0 ;
- map_call.seg_target_address =0xd000;
- map_call.new_page_map_length = new_length;
- map_call.offset_new_page_map = FP_OFF(new_map_ptr);
- map_call.seg_new_page_map = FP_SEG(new_map_ptr);
- map_call.old_page_map_length = old_length;
- map_call.offset_old_page_map = FP_OFF(old_map_ptr);
- map_call.seg_old_page_map = FP_SEG(old_map_ptr);
-
- inregs.h.ah =0x56; /* Set up for Alter Page Map and Call EMM function */
- inregs.h.al = 0;
- inregs.x.dx = handle ;
- map_call_ptr = &map_call ;
- inregs.x.si = FP_OFF(map_call_ptr);
- segregs.ds = FP_SEG(map_call_ptr);
- int86x(EMM_INT,&inregs,&outregs,&segregs);
- if (outregs.h.ah != 0) return(1);
-
- return(0) ;
- }
-
-
- Figure 10: Implementing the MS-DOS Load Function 4BH This function
- implements the MS-DOS load function 4BH. It sets AL to 3 causing
- the function to load the file and apply the relocation factor
- without executing the file.
-
- int load_overlay(load_file_name,relocation_ptr,page)
- char *load_file_name ;
- char *(*relocation_ptr);
- unsigned int page; /* physical page at which to load */
-
- {
- struct reloc {
- unsigned int load_seg; /* Which segment to load file */
- unsigned int reloc_factor; /* Which segment to use for
- relocation */
- } reloc_struct;
- struct reloc *reloc_struct_ptr;
-
- reloc_struct.load_seg = FP_SEG(*relocation_ptr) + (page * 0x400);
- reloc_struct.reloc_factor = FP_SEG(*relocation_ptr) + (page * 0x400);
-
- inregs.h.ah = 0x4B ; /* Dos Exec function code */
- inregs.h.al = 3; /* load but do not execute */
- inregs.x.dx = FP_OFF(load_file_name);
- segregs.ds = FP_SEG(load_file_name);
-
- reloc_struct_ptr = &reloc_struct ;
- inregs.x.bx = FP_OFF(reloc_struct_ptr);
- segregs.es = FP_SEG(reloc_struct_ptr);
-
- intdosx(&inregs, &outregs, &segregs);
-
- }
-
-
- Figure 11: NAME Start
-
- ; Use the DOSSEG directive to ensure that the code segment is the
- ; first segment in the module. Since this piece of code will be
- ; loaded in at the page frame at D000:0000H the Alter Page Map and
- ; Call will use D000:0000H as the entry point.
-
- DOSSEG
-
- data SEGMENT PUBLIC 'DATA'
- Data ends
-
- CODE SEGMENT PUBLIC 'CODE'
- ASSUME CS:CODE, DS:DATA
- start proc far
- push ds
- PUSH dx
- mov DX,data ; set up data seg into ds
- mov ds,dx
- pop dx
- mov ah, 09 ; set function code for DOS display string
- mov dx, offset enter_msg
- int 21H
- mov si, offset handle_name
- mov ax, 5401H ; set function to search for named
- ; handle
- int 67H ; and invoke EMM
- or ah, ah
- jnz exit
-
- mov si, offset map_call ; Set up registers for Alter Page
- ; Map and Call
- mov al, 0 ; indicate that values are pages
- mov ah, 56H ; set function to map and call
- int 67H ; invoke emm
- or ah, ah
- jnz exit
-
- mov ah, 09 ; set function for DOS display
- ; string
- mov dx, offset exit_msg
- int 21H
- exit:
- pop ds
- ret
- start endp
- CODE ENDS
- data SEGMENT PUBLIC 'DATA'
- ;
- cr equ 0Dh
- lf equ 0AH
- ;
- log_phys_map_struct STRUC
- log_page_number DW ?
- phys_page_number DW ?
- log_phys_map_struct ENDS
-
- map_call_struct STRUC
- target_address DD ? ; Pointer to which EMM will transfer
- ; control
- new_page_map_length DB ? ; Number of new pages to be mapped on
- ; call.
- new_page_map_ptr DD ? ; Pointer to array of
- ; log_phys_map_struc.
- old_page_map_length DB ? ; Number of pages to mapped on
- ; return.
- old_page_map_ptr DD ? ; pointer to array of
- ; log_phys_map_struc
- reserved DW 4 DUP (?)
- map_call_struct ENDS
- ;
- new_map log_phys_map_struct <1,1> ; mapping before call
- old_map log_phys_map_struct <0,0> ; mapping after call
-
-
- map_call map_call_struct <0D0004000H,1,new_map,1,old_map>
-
- handle_name db 'mapcall',0 ; handle name is ascciz string
- enter_msg db 'Entering Module 1',cr,lf,'$'
- exit_msg db 'Exiting Module 1',cr,lf,'$'
- Data ends
- end start
-
-
- Figure 12: NAME Start (MODULE2.EXE)
-
- DOSSEG
-
- data SEGMENT PUBLIC 'DATA'
- Data ends
-
- CODE SEGMENT PUBLIC 'CODE'
- ASSUME CS:CODE, DS:DATA
- start proc far
- push ds
- PUSH dx
- mov DX,data ; Set up date segment
- mov ds,dx
- pop dx
- mov ah, 09 ; Set function code for DOS display string.
- mov dx, offset enter_msg
- int 21H
- mov ah, 09 ; Set function code for DOS display string.
- mov dx, offset exit_msg
- int 21H
- pop ds
- ret
- start endp
- CODE ENDS
-
- data SEGMENT PUBLIC 'DATA'
- cr equ 0Dh
- lf equ 0AH
- enter_msg db 'Entering Module 2',cr,lf,'$'
- exit_msg db 'Exiting Module 2',cr,lf,'$'
- Data ends
- end start
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Vol. 3 No. 2 Table of Contents
-
- Microsoft(R) Windows Adapts to the Unique Needs of the Japanese Market
-
- A Japanese version of Microsoft Windows offers support for double-byte kanji
- characters and has the ability to convert kana characters into kanji. It
- also enables software vendors to build graphical applications that run on
- many disparate Japanese hardware systems despite their incompatibility.
-
-
- Utilizing OS/2 Multithread Techniques in Presentation Manager Applications
-
- While OS/2 provides the preemptive multitasking that Windows lacks, the
- Presentation Manager's message-based architecture can cause problems when
- doing lengthy processing. OS/2 threads offer alternative solutions to the
- problems that occur when programming in Presentation Manager.
-
-
- OS/2 LAN Manager Provides a Platform for Server-Based Network Applications
-
- The OS/2 LAN Manager API, with its named pipe and remote procedure call
- facilities, offers a rich set of functions for building network intrinsic
- applications. These applications can exploit the potential of distributed
- processing from an OS/2 or DOS workstation.
-
-
- Writing OS/2 Bimodal Device Drivers: An Examination of the DevHlp API
-
- Device drivers are the necessary linkage between the operating system and
- peripheral devices that enable you to use special hardware with standard
- applications software. Our study of OS/2 continues with an in-depth look at
- the unique capabilities of OS/2 device drivers.
-
-
- Exploring the Structure and Contents of the MS-DOS(R) Object Module
- Format
-
- A programmer does not usually need to think about the contents of the
- intermediate files prepared for the linker when using commercial compilers.
- This excerpt from The MS-DOS(R) Encyclopedia examines the object module
- format and provides insights into the operation of LINK and LIB.
-
-
- A Guide to Program Editors, the Developer's Most Important Tool
-
- Text editors for programmers have improved greatly, prompting MSJ to conduct
- an evaluation of twelve editors. The three editors that we thought were
- best──BRIEF(R), ME, and VEDIT PLUS──are reviewed in depth, and a features
- chart gives you a good idea of what all twelve can do.
-
-
- EDITOR'S NOTE
-
- This issue of MSJ covers the broadest range of topics yet. In addition to
- our continuing coverage of the OS/2 systems with articles on the OS/2 LAN
- Manager, bimodal device drivers, and the Presentation Manager, we have an
- article on developing software for the Japanese market, a tutorial on the
- MS-DOS(R) object file format, and a comprehensive look at available
- program editors.
-
- As time goes by, we pass yet another milestone in the progress of OS/2.
- With the shipment of Version 1.0 of the standard edition, OS/2 has become
- a users' as well as a developers' product, and many developers have begun
- to focus on the advanced managers. The Presentation Manager is no longer
- just a concept or a specification; now that it is in developers' hands, it
- is a real development environment. In this issue, Ray Duncan explains how
- to write a device driver for OS/2, and Charles Petzold examines the use of
- OS/2 threads within Presentation Manager
- programs.
-
- OS/2 has the potential not only to improve the applications we associate
- with MS-DOS, but to open the way for entirely new classes of applications.
- Among the most exciting of these is distributed processing. PC networking
- to date has in most cases meant little more than file and printer sharing.
- We present an overview of the possibilities for the development of
- server-based distributed processing applications with the OS/2 LAN Manager.
-
- Recently in talking with subscribers, we were reminded just how important,
- and how personal, the choice of a program editor is to the professional
- programmer. We heard many different opinions, all of which were
- uncharacteristically strong. Given the importance of the subject, we
- thought it worthwhile to depart from our normal subject material and
- commissioned a comprehensive review of the available editors. The results
- were surprising. Please drop us a line if you think we should review
- other categories of programmer tools.
-
- We want this to be your Journal. Please write and tell us how we can make
- it better for you.──Ed.
-
- Masthead
-
- JONATHAN D. LAZARUS
- Editor and Publisher
-
- EDITORIAL
-
- TONY RIZZO
- Technical Editor
-
- CHRISTINA G.DYAR
- Associate Editor
-
- JOANNE STEINHART
- Production Editor
-
- GERALD CARNEY
- Staff Editor
-
- KIM HOROWITZ
- Editorial Assistant
-
- ART
-
- MICHAEL LONGACRE
- Art Director
-
- VALERIE MYERS
- Associate Art Director
-
- CIRCULATION
-
- WILLIAM B. GRANBERG
- Circulation Manager
-
- L. PERRIN TOMICH
- Assistant to the Publisher
-
- DONNA PUIZINA
- Administrative Assistant
-
- Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
- in part or in whole without permission is prohibited.
-
- Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
- NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
- Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
- President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
- William Neukom, Secretary.
-
- Microsoft Corporation assumes no liability for any damages resulting from
- the use of the information contained herein.
-
- Microsoft, XENIX, CodeView, and MS-DOS are registered trademarks of
- Microsoft Corporation. IBM and PC/AT are registered trademarks of
- International Business Machines Corporation. Personal System/2 and PS/2 are
- trademarks of International Business Machines Corporation. BRIEF is a
- registered trademark of UnderWare, Inc. CP/M is a registered trademark of
- Digital Research, Inc. dBASE and dBASE III PLUS are registered trademarks of
- Ashton-Tate Corporation. Hercules is a registered trademark of Hercules
- Computer Technology. Intel is a registered trademark of Intel Corporation.
- Macintosh is a registered trademark of Apple Computer, Inc. NEC is a
- registered trademark of NEC Corporation. Paradox is a registered trademark
- of Ansa Software, a Borland Company. 3+Open is a trademark of 3Com
- Corporation. Solution Systems is a registered trademark of Solution Systems.
- UNIX is a registered trademark of American Telephone and Telegraph Company.
- WordStar is a registered trademark of MicroPro International Corporation.
- WYSE is a trademark of WYSE Technology.
-
- ████████████████████████████████████████████████████████████████████████████
-
- Bringing Windows to the Expanding Japanese Market
-
- by Tom Sato and Lin F. Shaw
-
- Machine independence is a cornerstone of the Microsoft(R) Windows
- philosophy. While this particular issue is largely ignored in the United
- States-due to the fact that the PC environment is dominated by IBM(R) PC
- AT(R) machines and compatibles──in Japan, Windows-based software is truly
- compatible across incompatible hardware architectures.
-
- Microsoft Windows has given the Japanese community of independent
- software vendors (ISVs) the opportunity to develop applications with
- only one set of source codes for a wide range of machines, thus
- eliminating the laborious adaptation of software to different machines.
- For Western ISVs there is an added advantage. Windows applications
- written for IBM PCs and compatibles run on all the various Japanese
- computers. Windows-based applications, therefore, present a tremendous
- export opportunity for Western ISVs, since Japan is a country where real
- growth in the software industry has yet to come (see Figures 1 and 2).
-
- Microsoft Windows Version 1.03 was first released by NEC in December 1986
- for use on its PC-9801 series of personal computers, and in its first six
- months sold over 25,000 copies. Matsushita has also joined the original
- family of Windows equipment manufacturers (OEMs) by releasing their own
- adaptations of Windows for their new machines at the September 1987 Tokyo
- DATA show (see Figures 3 and 4).
-
-
- The Japanese Market
-
- In 1982, NEC launched the PC-9801 series. Fourteen compatible variations
- later, with its millionth unit shipped last March, the PC-9801 series
- dominates the Japanese PC market, with a market share estimated at over
- 60 percent. The rest of the market is split among various companies, all
- of which have released incompatible MS-DOS(R) machines.
-
- In spite of these numbers, however, the fact is that the penetration rate
- of personal computers in the Japanese office is nowhere near that of the
- United States, as many Japanese businessmen have yet to become accustomed
- to PCs. The primary reason is that the Japanese language has a great
- many character sets and kanjis (Chinese characters). This has prevented
- the spread of mechanical typewriters──they have more than 4000 characters
- and are very clumsy to use──and, therefore, computers. Amazingly, most
- business correspondence in Japan is still done by hand.
-
- Fortunately, there is a sign that this is all going to change. Several
- years ago, some of the electronics giants started to sell electronic
- Japanese typewriters. For the most part, these are 8-bit computers with
- an LCD screen, a thermal transfer printer and built-in syllabic kana to
- kanji conversion software. These machines provide more capability,
- portability, efficiency, and also better prices than the previous
- generation of mechanical typewriters. This is the latest booming
- business in Japan, with an installed base of more than 5 million
- units.
-
- Nevertheless, these electronic typewriters still have limitations──they
- are not as flexible as word-processing software on 16-bit PCs; storage
- is limited and the editing screen is really too small for general office
- use. However, the popularity of the Japanese electronic typewriters
- has led to significant increases in the sale of substantially more
- powerful 16-bit PCs.
-
- The standard configuration for a typical Japanese PC is an 80286 running
- at 10 MHz, a 640x480 pixel screen, 640Kb of RAM, 256Kb of ROM for kanji
- fonts, and dual 1.2Mb floppy disk drives. Machines with an internal 20Mb
- hard disk drive are also becoming more popular, and there are now
- machines with higher screen resolution and more detailed kanji fonts
- available. However, the speed of Japanese computers is slower than that of
- their counterparts in the United States. This is largely due to a great
- deal of overhead, for instance searching for kanji bitmaps and having to
- deal with larger screen and kana-kanji interfaces.
-
-
- Japanese Language
-
- Modern Japanese is written in a mixture of kanji characters and two types
- of kana signs. There are 46 basic kana signs; 45 basic syllables arranged
- as in Figure 5, consisting of five vowels in the order a, i, u, e, o and
- then five vowels preceeded in turn by nine consonants in the order k, s
- (or alternatively sh), t (or ts), n, h (f), m, y, r, w, and the final
- consonant for kana, n.
-
- Two small strokes on the upper right hand corner of a character are
- added as voiced marks or dakuten. They apply to kana beginning with k, s,
- t, and h and represent 20 more syllables beginning with voiced
- consonants g, z, d, and b respectively. A small round circle on the
- upper right hand corner is a semi voicing sound mark (han-dakuten). It
- applies to the kana beginning with h and represents five further
- syllables begining with voiced consonants p. Further, nine of the kanas
- are sometimes written in small characters to form dipthongs (see
- Figure 6).
-
- There are two types of kana signs: kata-kana and hira-gana There is a
- one-to-one correspondence between the kata-kana and hira-gana; for
- example, for each syllable there is both a hira-gana letter and kata-kana
- letter. Hira-gana is most commonly used with a mixture of kanji, and kata-
- kana is always used for foreign words. Kata-kana is a simplified form of
- hira-gana, written for the most part in simple strokes, whereas hira-ganas
- are written with curves. On an 8x8 bitmap, hira-gana is too intricate to
- describe. Therefore, both the Japanese keyboard and the Japanese 1-byte
- character code for alphanumeric characters contain only the kata-kana
- (see Figure 7).
-
-
- Kanji Characters
-
- Kanji characters are used frequently to represent one or more kana
- characters. There are many homonyms in Japanese and the use of kanji
- clarifies the meaning of the written word. Nouns and verbs are typically
- kanjis or a mixture of hira-ganas and kanjis. A typical kanji dictionary
- lists over 15,000 kanji characters. However, the Japanese elementary
- schools only teach 1878 of them and in practice about 3500 kanjis have
- normal daily use.
-
- The same kanji character may have different pronunciations depending on
- its context or the other kanji with which it forms a word. For example, a
- kanji character for the word "leaf" has three pronunciations: ha, ba, and
- yo-u. Conversely, the same pronunciation is used for many kanji; for
- example, as many as twenty different kanjis can be pronounced ai.
-
-
- Text Layout
-
- Japanese is traditionally written vertically in columns running from top
- to bottom and from right to left across the page. Most books and
- newspapers are written this way. However, it can also be written
- horizontally as English is. Business correspondence, in fact, is usually
- written horizontally. Most Japanese software, therefore, displays
- horizontally and only supports vertical columns when printing a document
- or on a screen in sophisticated desktop publishing software.
-
- Japanese text is written without spaces between words. The use of a
- mixture of kanji and kana in a regular sentence provides implicit breaks
- to a sentence and the Japanese would have a very difficult time reading
- text of only kana characters. It would be analogous to reading English
- text without spaces between words.
-
- Japanese text can wrap from one line to another at any point, since there
- are no spaces between words. This includes two successive kanji which
- make up one logical "word." There is no need to justify Japanese text, as
- all Japanese characters are the same size.
-
-
- Shift-JIS Code
-
- Japanese Industrial Standards (JIS) define two levels of kanjis for
- computers. The JIS C 6226 standard defines the image and the code of the
- 3500 most commonly used kanji characters as level 1 kanjis. Level 2
- defines the optional additional 2500 kanjis which are more obscure and are
- used for the names of people and places. Most 16-bit computers in Japan
- now support level 2, making the number of kanjis usually available to
- Windows more than 6000. They are stored in bank-switched ROMs as 16x16
- pixel images.
-
- Kanjis, as well as hira-ganas, kata-kanas, alphanumeric characters, Greek
- and Russian characters, and other signs supported in ASCII character
- sets, are coded in the JIS coding scheme. JIS was designed like ASCII to
- be a transmission standard as well as a storage standard. When
- transmitting data over communications lines, reliability is extremely
- important. That is why parity exists for 7-bit ASCII.
-
- The JIS standard was also encoded in 7 bits to use parity. In order to be
- able to distinguish between 7-bit ASCII and 7-bit JIS, the ASCII shift-in
- (SI) and shift-out (SO) controls were used. A kanji character is composed
- of two successive 7-bit patterns, limiting us to 16,384 values. In order
- to avoid the 32 control characters, space, and rub-out, there are only
- 94x94 (8836) available and defined characters.
-
- As the use of parity in personal computers became less important, and
- since many U.S. applications did not understand SI and SO, Microsoft
- defined a new kanji encoding scheme for MS-DOS; the Microsoft Kanji
- Encoding Scheme, which uses two 8-bit values. It is commonly known as the
- Shift-JIS Code, because the character codes of JIS have been shifted.
-
- Figures 7 and 8 describe how this works. Of all the codes available
- between 00H and FFH, 00H to 7FH are ASCII characters and control codes,
- and A0H to DFH are kata-kana characters. This leaves 64 positions for
- the first byte of the double-byte value. The second byte could be between
- 00H and FFH. Since 64x256 is 16,384, there is more than enough room for
- all the JIS characters.
-
- Since JIS has a 94x94 matrix, Microsoft decided to map it into a 47x188
- matrix in the set of 64x256 possibilities. This allowed 68 values to be
- avoided for the second byte. The control characters between 00H-1FH and
- 7FH-FFH were not used in order to avoid conflict with U.S. applications,
- nor were FDH and FEH used, because CP/M(R) 86 was using them when the
- scheme was introduced. Also, 20H-3FH were avoided in order to make
- parsing of characters such as ";", "(,)", and ":" easier for U.S.
- language compilers.
-
- Figure 8 shows how the Microsoft Kanji Encoding Scheme is shifted from
- the JIS coding scheme without actually changing the original JIS
- ordering. Just remember that a code between 81H-9FH and E0H-FCH means
- that the following code is the second byte of the double-byte character
- and together they give the code of a kanji character.
-
-
- Kana-Kanji Conversion
-
- With all these hira-gana, kata-kana and kanji characters, making a
- computer to support the Japanese language becomes complex. On the MS-DOS
- level, to input Japanese, a kana-kanji conversion program is installed
- as a device driver. There are various kana-kanji conversion programs on
- the market and most of them are porting their software to run under
- Windows. Usually kana-kanji conversion units are packaged free of charge
- with applications software. Companies lacking a kana-kanji conversion
- program can buy a license from kana-kanji software vendors.
-
- These conversion programs are typically activated by pressing a "hot
- key." In the case of the NEC(R) PC-9801 series, for example, the hot-key
- combination is CTRL-XFER and the bottom of the screen becomes the
- conversion window.
-
- There are various methods of input but all have kata-kana input and
- romanji (Roman character) input. Although the keyboard supports all the
- kata-kana characters, many Japanese are familiar with the standard
- English QWERTY typewriter keyboard and prefer to input using Roman
- characters. For example, the word "kanji" is typed K A N J I and as you
- type it in, the conversion program accepts the Roman characters and
- displays the correct hira-ganas. When the <XFER> key is pressed, the
- program searches through its dictionary to find the kanji characters for
- that word.
-
- This process is complicated for various reasons, including the Japanese
- homonyms mentioned earlier. Secondly, the inflectional endings of
- Japanese verbs increase the number of word roots so much that the
- dictionary may have to contain well over 100,000 word roots. Obviously,
- this would require an enormous amount of memory.
-
- The problem is solved by software that draws on a grammar of Japanese
- inflection to analyze the phonetic spelling typed in by the user. The
- software displays the most appropriate and most commonly used kanji
- combinations. Good software will give the correct kanji approximately 80
- percent of the time. If the software gives the wrong kanjis, the user can
- hit the <XFER> key again and the next kanji choice will appear. To see
- all the different ways of spelling the word, you press <SHIFT>-<XFER>.
- Some conversion packages can convert multiple phrases, even a whole
- sentence. Figure 9 shows the kana-kanji conversion unit as it would
- appear in a Windows session.
-
-
- Kanji Classification
-
- Microsoft Windows 1.03 applications can be classified according to their
- abilities to process kanji input. Please note that this specification is
- only intended for Windows 1.03. There will be additions in the Windows
- 2.0 specification.
-
- Level 1: Original U.S. Applications. These applications are not able to
- treat double-byte kanji text. Most of the first releases of U.S.
- applications are level 1 applications.
-
- Level 2: Modified U.S. Applications. These applications can process
- double-byte kanji characters as a single entity. They accept and display
- shift-JIS kanji inputs, advance the cursor and handle backspaces with
- kanji text, sort kanji text, and do right justification on kanji text.
- However, these applications do not handle in-line kana-kanji conversion,
- and the conversion process is transparent to these applications. The
- conversion is done by a separate conversion module called KKAPP which is
- activated and deactivated using special keys. In-line conversion means
- that the kana and kanji input actually takes place in the applications
- window and not in the pop-up windows at the bottom of the screen. KKAPP
- defines the user interface for kanji conversion for all level 2
- applications. Most kanji applications are currently at this level.
-
- Level 3: Applications with Full Kanji Support. These applications can
- handle kana-kanji conversion. They call the KKAPP using the level 3
- application function ConvertRequest. They allow the user to change a
- kanji selection from a list of kanji candidates having the same
- pronunciation, input kanji strings without using an explicit convert
- command (free input), do full sentence or full paragraph conversion, and
- select kanji candidates from a menu list using the mouse.
-
-
- Conversion Under Windows 1.03
-
- Under MS-DOS, the conversion module traps the keyboard input, converts it
- to the appropriate 2-byte characters and then transfers those
- characters to MS-DOS. Under Windows there are six modules that are
- involved in kana-kanji conversion: the keyboard driver, USER, FEP,
- KKAPP, KKLIB, and the application. Figure 10 describes each of the
- steps.
-
-
- Keyboard Driver
-
- Kana-kanji conversion starts with the user typing in a key. This keyboard
- input is processed by the Windows keyboard driver and sent to USER.
-
- The USER library module turns keyboard input into a Windows message
- (either WM_KEYUP or WM_KEYDOWN) and sends the message to the appropriate
- module. If WH_KEYBOARD has been set using SetWindowsHook, then USER sends
- the keyboard messages to the keyboard filter; otherwise, USER sends the
- keyboard message to the current application.
-
- The KKLIB library module sits between USER and KKAPP and controls the
- flow of keyboard input. Upon request from KKAPP, KKLIB will install the
- keyboard filter function to receive all the keyboard input. In the filter
- function, KKLIB decides whether to send the keyboard message to KKAPP or
- to return the keyboard message to USER without processing it (as when the
- kana-kanji conversion mode is turned off).
-
- KKLIB also provides an entry point in level 3 applications for kana-kanji
- conversion requests or inquiries. This function in KKLIB
- (ConvertRequest()) in turn translates these requests into Windows
- messages (WM_ CONVERTREQUEST) and sends the messages to KKAPP.
-
- The KKAPP application module does the kana-kanji conversion. Depending
- on which kind of application is using KKAPP (level 2 or level 3), KKAPP
- will take the appropriate actions. If a level 2 application is the
- active window and the conversion mode is on, KKAPP will open up its
- conversion window to display the conversion process, and send the
- final result to the application through WM_CHAR messages. If a level 3
- application is active, KKAPP does not display the conversion process
- and simply answers convert requests from the application. One KKAPP is
- required for each FEP.
-
- The front end processor module does the actual conversion. This module
- has the dictionary and the conversion algorithm and KKAPP interfaces
- directly to it. It is installed as an MS-DOS device driver.
-
- A level 2 application is not involved in kana-kanji conversion. It simply
- receives the converted results in WM_CHAR messages from KKAPP. A level 3
- application does participate in kana-kanji conversion by calling
- ConvertRequest() and receives the results back in the KanjiStruct
- parameter.
-
-
- Double-Byte Kanji
-
- Writing a Windows application for the Japanese market involves a number
- of things. First, all messages and resources in the resource file need to
- be translated into Japanese. The double-byte support code of the
- resource compiler is enabled when the keyword KANJI is seen in the
- resource file. This keyword must be followed by four numbers stating the
- two ranges of valid first byte ranges of kanji. Figure 11 provides an
- example of the translated resource file for the clipboard desktop
- application.
-
- Windows virtualizes 2-byte support by providing string handling routines.
- Applications should not make any assumption about the system they are
- running on. Using the function AnsiUpper to convert to an upper character
- or the function AnsiLower to convert to a lower character enables the
- same application to run correctly both in the U.S. and in other
- countries. Windows further provides the AnsiNext call to scan forward and
- AnsiPre to scan backwards. It is important to use these calls so that an
- application does not get out of sync and misinterpret consecutive
- kanjis as different kanjis.
-
- Another problem is that the second byte of the shift-JIS character set
- overlaps with Roman characters starting at 40H. Applications should be
- careful not to mistreat the second byte of a kanji as a Roman character,
- particularly when implementing the searching function or when parsing
- path names looking for the '\' file separator.
-
- In screen editing, applications that use Windows Edit Control will run
- without modification in Japan. Applications that do not use Edit
- Control should buffer two WM_CHAR messages to make one single TextOut()
- call when 2-byte kanjis are encountered. When the application is
- advancing, backspacing, positioning the cursor, and line-wrapping on
- the screen, it must treat 2-byte kanjis as one entity.
-
- Lastly, sorting that involves kanji is slightly more complicated. The
- shift-JIS character set places its 1-byte kanas in the A0H-CFH range,
- and the kanjis in the two ranges 80H-9FH and E0H-FCH. In Japan, 1-byte
- kanas are sorted after 1-byte Ro-man characters in the 20H-7EH range, but
- before all the 2-byte characters. This necessitates swapping kana
- characters with the kanjis which are in the 80H-9FH range when doing a
- sort.
-
-
- In Conclusion
-
- Microsoft is currently working on an additional specification for the
- kana-kanji interface for Windows 2.0. In the meantime, ISVs should be
- able to see that a major opportunity exists to export Windows-based
- applications to the Japanese market. Hopefully, the information
- provided here will offer some insight into the work required to pursue
- it. ISVs interested in developing Japanese Windows applications are
- urged to contact the OEM Technical Support Group, Microsoft KK, Sanban cho
- Yayoi kan, 6-2 Sanban cho, Chiyoda ku, Tokyo, JAPAN, for more detailed
- information. We look forward to hearing from you.
-
-
- Figure 8: Microsoft Kanji Encoding Scheme
-
- 00
-
- 10 21 7E
-
- 20 21┌───────────────────────┐
- │ │
- 30 │ │
- │ JIS level 1 │
- 40 │ │
- │ │
- 50 ├───────────────────────┤
- │ │
- 60 │ JIS level 2 │
- │ │
- 70 │ │
- 7E└───────────────────────┘
- 80 81┌──────────────┐┌───────┬──────────────────────┐
- │ Microsoft translation of JIS level 1 │
- 90 │ ││ │ │
- ├──────────────┤├───────┼──────────────────────┤
- A0 9F└─────────────┘└───────┴─────────────────────┘
- Microsoft translation of JIS level 2, part 1
- B0
- │ │ │ │ │ │
- C0 40 7E 80 9E 9F FC
- │ │ │ │ │ │
- D0 │ │ │ │ │ │
-
- E0 E0┌──────────────┐┌───────┬──────────────────────┐
- EF│ Microsoft translation of JIS level 2, part 2 │
- F0 F0├──────────────┤├───────┼──────────────────────┤
- FCExtra room for extensions to the JIS kanji standard
- └──────────────┘└───────┴──────────────────────┘
-
- 00 10 20 30 40 50 60 70 80 90 A0 B0 C0 D0 E0 F0
-
-
- Figure 10: The kana-kanji conversion process under Windows Version 1.03.
-
- ┌─────────────────────────────┐
- │ Keyboard Drivers │
- ░░ Level 1 │ │
- └─▒──▓──────────────────────░─┘
- ▒▒ Level 2 ▒ ▓ ░
- ┌──────────────────────────┐
- ▓▓ Level 3 │ USER.EXE │
- │ ▓▓▓▓▓▓▓ │
- └─▒──▓──────────▓─▒▒▒─▓────░─┘
- Keyboard ▓ ▒ ▒ ▓ ░
- hook set Conversion ░
- ▒ ▓ mode off ░
- ┌───────────┐ ▓ ▒ ▒ ▓ No
- │ KKLIB ▓▓▓▓▓▓▓ ▒ ▒ ▓ keyboard
- │ ▒▒▒▒▒▒▒▒▒ ▒ ▓ hook
- └─────▒──▓────┘ ▒ ▓ ░
- ▒ ▓ ▒ ▓ ░
- ┌─────────────┐ ┌───────────┐ ▒ ▓ ░
- │ FEP │───│ KKAPP │ ▒ ▓ ░
- │ │ │ │ ▒ ▓ ░
- └─────────────┘ └─────▒──▓───┘ ▒ ▓ ░
- ▒ ▓ ▓ ▒ ▓ ░
- Conversion Virtual ▒ ▓ ░
- result kanji ▒ ▓ ░
- ▒ ▓ key ▒ ▓ ░
- ▒ ▓ interface ▒ ▓ ░
- ▒ ▓ ▓ ▒ ▓ ░
- Conversion request and ░
- ▒ ▓ results back ▒ ▓ ░
- ▒ ▓ ▓ ▒ ▓ ░
- ╔═══════▓═══════════════════╗
- ║ Applications ║
- ║ ║
- ╚════════════════════════════════╝
-
- ████████████████████████████████████████████████████████████████████████████
-
- Utilizing OS/2 Multithread Techniques in Presentation Manager Applications
-
- Charles Petzold
-
- Microsoft(R) Windows is a multitasking operating environment──but not really.
- As most Windows programmers know, Windows is actually a "nonpreemptive
- multitasking" environment. It does not perform the preemptive time-
- slicing we normally associate with a multitasking system. Instead, Windows
- multitasks programs based on the presence of "messages" (which often
- represent keyboard and mouse input) in the programs' message queues.
-
- When a Windows program calls the GetMessage function to retrieve the next
- message from its message queue, and the message queue is empty, Windows
- suspends the program. Windows then switches to another program with a
- nonempty message queue. This causes that other program to return from its
- own GetMessage call to process the message. Only one Windows program is
- running at any time. The rest are suspended in the GetMessage function.
-
- Windows programmers are well aware of the problems associated with this
- form of nonpreemptive multitasking. If a Windows program requires a long
- period of time to process a message, then other programs running under
- Windows are effectively halted for the duration. Windows programmers must
- use some special techniques (which we'll look at in this article) when
- doing lengthy processing to prevent the program from suspending the rest of
- the system.
-
- Microsoft OS/2 Presentation Manager is a windowing environment with a
- message-based architecture much like that of Microsoft Windows. But unlike
- Windows, the OS/2 Presentation Manager runs under a true priority-based
- preemptive multitasking operating system.
-
- At first, the preemptive multitasking of the OS/2 systems would seemingly
- eliminate the problems associated with the nonpreemptive nature of
- Windows. You might conclude that Presentation Manager programs can spend
- as much time as they need processing a message without worrying about
- suspending other programs. But this is not so. The problems that arise
- when doing lengthy processing in Windows result more from the message-based
- architecture rather than from nonpreemptive multitasking. In that
- sense, the Presentation Manager is the same as Windows.
-
- The real difference is that the Presentation Manager provides more and
- better solutions to the problem of lengthy processing. That's what we'll
- explore here.
-
-
- The "Big Job" Problem
-
- Presentation Manager programs can usually process most keyboard and mouse
- input very quickly. In a word processing program, for example, a
- character typed from the keyboard need only be inserted into the document
- and displayed on the screen. But many programs must also carry out commands
- that require more lengthy processing. My term for this lengthy
- processing is the "big job."
-
- In a Presentation Manager spreadsheet program, the big job is a
- recalculation or the execution of a long macro. In a database program, the
- big job is a file sort or indexing. In a word processing program, it's a
- pagination or spelling check. In a CAD program, it's redrawing the
- screen. In a communications program, it's reading the serial port when an
- incoming character is not immediately available. In most any
- Presentation Manager program, printing is a big job.
-
- A big job is anything that takes more than 1/10 second. This is based on
- the recommendation in the Presentation Manager documentation that
- programs take no more than 1/10 second to process a message. Although
- this is a guideline rather than a hard-and-fast rule, I'll refer to it here
- as the "1/10 second rule."
-
- To explore the big job problem, let's write a Presentation Manager
- program that does a big job-specifically, a calculation called the
- "savage" benchmark that is often used to test floating-point speed. The
- program will allow repeating the savage calculation 10, 100, 1000, or 10,000
- times depending on a menu selection.
-
- I'll use the "alternate" floating-point library in this program so that the
- calculation isn't affected by the presence of an 80287 math coprocessor.
- The program will have menu options to start the calculation running and-if
- possible-to abort it before it's finished. The 10,000 repetitions of the
- savage benchmark calculation takes about 3 minutes on an 8-MHz IBM(R) PC/AT(R
- Even 10 repetitions violate the 1/10 second rule.
-
- Five different versions of this program will be examined, with each
- successive version more sophisticated in its approach to multitasking under
- the Presentation Manager.
-
-
- The Naive Approach
-
- The simplest approach to a big job is shown in the BIGJOB1 program in
- Figures 1 and 2. Most of the code in BIGJOB1's client window procedure,
- which is called ClientWndProc, handles WM_COMMAND messages from the
- program's menu. When you select an option from the Repetitions menu, BIGJOB
- unchecks the currently selected option and checks the option you choose.
- When you select Start from the menu, BIGJOB1 disables the Start option,
- enables the Abort option, and begins the calculation. After it's finished
- with the big job, the program reenables the Start option and then exits the
- window procedure.
-
- Because BIGJOB1 does the entire calculation in response to a WM_COMMAND
- message from the menu, it obviously violates the 1/10 second rule. BIGJOB1
- is a bad program. I show it only to illustrate how such programs clog up the
- rest of the Presentation Manager.
-
- While BIGJOB1 is doing the big job, you cannot switch to another program
- running under the Presentation Manager. The system seemingly ignores all
- keyboard and mouse input until the calculation is finished. Although the
- Abort option is present on BIGJOB1's menu, you can't use the keyboard or
- mouse to select that option. Once you begin the big job, you have to wait
- until it's finished to be able to do anything else.
-
- At first, this seems disturbing. Isn't the Presentation Manager supposed to
- be multitasking? And if so, why does one program apparently cause the whole
- system to grind to a halt? What is happening here is a predictable result of
- the message-based architecture of the Presentation Manager.
-
-
- Using Messages
-
- Programs that run under the Presentation Manager are message driven. In the
- normal case, a program spends most of its time suspended in WinGetMsg (the
- Presentation Manager equivalent of the Windows GetMessage function)
- waiting for a message.
-
- Each window created by a program has a window procedure that processes
- messages to the window. At least one of these window procedures──the
- window procedure for the client window──is located in the Presentation
- Manager program. The other window procedures (such as those for the frame
- window, the title bar window, and the menu window) can be found in the
- Presentation Manager PMWIN.DLL dynamic-link library.
-
- Some of the messages for a window are stored in the program's message
- queue. These messages are called "queued" messages and are sometimes said to
- be "posted" to the queue. The queued messages are retrieved from the message
- queue when a program calls WinGetMsg and are dispatched to the window
- procedure by WinDispatchMsg. Other messages are sent directly to the window
- procedure, bypassing the program's message queue.
-
- Regardless of whether a message is posted to a message queue or sent
- directly to a window procedure, and regardless of whether a window
- procedure is contained in the program or in a dynamic-link library,
- messages to windows created by a thread of execution are always processed
- in that thread. A particular thread of execution can only do one thing at
- a time. A thread cannot be multitasked with itself. This seems obvious, yet
- the message-based architecture of the Presentation Manager sometimes
- obscures this simple fact.
-
- When you select Start from BIGJOB1's menu, the program begins the big job.
- You then try to use the Alt-Tab key combination to switch to another
- program. The window that must process these keystrokes is BIGJOB1's frame
- window. But the window procedure for the frame window must run in the same
- thread as the client window, and the client window is busy doing the big
- job. This means that the Alt-Tab keyboard message cannot be processed
- until the calculation is finished, and until BIGJOB1 exits ClientWndProc and
- then calls WinGetMsg to retrieve the message from the queue. This explains
- why the Presentation Manager seems to be ignoring keyboard input while
- BIGJOB1 is calculating.
-
-
- Serialization of Input
-
- There may, however, be a way around this. As you know, you can also use the
- mouse to make another window active. Maybe that will work.
-
- To test this possibility, you must first position the mouse pointer on top
- of another program's window and then use the keyboard to select Start from
- BIGJOB1's menu. While it is calculating, you press the mouse button and...
- nothing happens.
-
- This again is initially disturbing. Because the Presentation Manager is a
- true multitasking system, the other program ought to be able to read that
- mouse click even while BIGJOB1 is calculating. The Presentation Manager
- should also be able to change the active window from BIGJOB1 to the other
- program.
-
- This does not happen for a couple of reasons. First, the Presentation
- Manager serializes all keyboard and mouse input in a system message queue.
- Keyboard and mouse input is passed one message at a time to an
- application's message queue based on which window has the input focus (in
- the case of keyboard messages) and which window is underneath the mouse
- pointer.
-
- The serialization of mouse and keyboard input in a system message queue is
- required to correctly handle "type-ahead" and "mouse-ahead" input from the
- user. One of the keystrokes or mouse clicks in the system message queue
- could have the effect of changing the active window and the focus window.
- Subsequent keyboard input must then go to that new window. This won't work
- if mouse and keyboard input is not passed to applications in the same
- order that it enters the system message queue. Therefore, a keyboard or
- mouse message cannot be passed to a particular application until all
- previous keyboard and mouse messages have been processed.
-
- In this particular case, another application cannot read a mouse message
- until BIGJOB1 processes all its keyboard input. The keyboard input that it
- hasn't processed is the release of the key that caused the menu to send the
- WM_COMMAND message that started the calculation.
-
- Thus, because BIGJOB1 renders itself resistent to keyboard or mouse input,
- it also prevents all other programs running under the Presentation Manager
- from getting keyboard or mouse input.
-
- Even if another program could read a mouse click, the Presentation Manager
- cannot change the input focus from BIGJOB1 to another program while BIGJOB1
- is busy doing the calculation. In order to change the input focus, the
- Presentation Manager must send a WM_SETFOCUS message to the window losing
- the input focus. That WM_SETFOCUS message is blocked because the window that
- must receive the message is part of BIGJOB1's thread, and BIGJOB1 is busy
- doing the big job.
-
- Messages are not like hardware interrupts. Although a window procedure can
- be sent a message as a result of calling WinDefWindowProc, and a window
- procedure can be sent a message as a result of calling some other
- Presentation Manager functions, these are examples of recursion in window
- procedures. Messages do not preemptively interrupt a thread and start
- execution someplace else in the same thread.
-
- Now that we've seen how BIGJOB1 effectively disables keyboard and mouse
- input in the Presentation Manager, the reason for the 1/10 second rule
- should be obvious. Presentation Manager programs must continually interact
- with the system, retrieving and processing their messages promptly.
-
-
- Not Exactly Like Windows
-
- As bad as BIGJOB1 is, the Presentation Manager can still multitask programs
- while BIGJOB1 is running. This is a difference between the Presentation
- Manager and Windows.
-
- If you have a Windows version of BIGJOB1 running under Windows along with
- the Windows CLOCK program, CLOCK will stop dead while BIGJOB1 is
- calculating. Under Windows, only one program can be running at any time.
-
- However, when you run BIGJOB1 in the Presentation Manager along with a
- Presentation Manager version of CLOCK (like the one included in my book,
- Programming the OS/2 Presentation Manager, Microsoft Press, 1988) you'll
- find that CLOCK continues to run while BIGJOB1 is calculating. CLOCK and
- BIGJOB1 run concurrently.
-
- CLOCK works by setting a timer and then processing a WM_TIMER message every
- second. A WM_TIMER message does not have to be serialized with the
- keyboard and mouse input. CLOCK can continue to receive these messages
- even if BIGJOB1 has clogged up keyboard and mouse input.
-
-
- Using a Timer
-
- Once you acknowledge that BIGJOB1 is a very bad Presentation Manager
- program, you are stuck with the problem of structuring it so that it works
- correctly. The use of a timer in CLOCK suggests one possible remedy: you can
- divide a big job up into little pieces, set the Presentation Manager
- timer, and do each little piece on receipt of a WM_TIMER message. This is a
- solution that works both in the Presentation Manager and in Windows.
-
- The BIGJOB2 program uses a timer to do the big job. The files BIGJOB2,
- BIGJOB2.C, and BIGJOB2.DEF are shown in Figure 3. Compiling BIGJOB2 also
- requires the BIGJOB.H and BIGJOB.RC files shown in Figure 1.
-
- When you select the Start option from BIGJOB2's menu, BIGJOB2 calls
- WinStartTimer to start the timer, disables the Start option, enables the
- Abort option, and initializes a few variables. The savage calculation is
- done once for each WM_TIMER message. Thus, for 1000 repetitions, the big
- job is finished after 1000 WM_TIMER messages.
-
- WM_TIMER messages are low-priority queued messages. If the message queue
- contains keyboard or mouse messages, these messages will be retrieved from
- the queue and processed before a WM_TIMER message. Thus, BIGJOB2 continues
- to read keyboard and mouse input and can allow the user to select Abort from
- BIGJOB2's menu, move or resize BIGJOB2's window, or to move to another
- program. The entire system-including BIGJOB2-continues to function normally
- all the time that BIGJOB2 is doing the calculation.
-
-
- Timer Problems
-
- The timer approach is not too bad for BIGJOB2, but it's easy to imagine
- cases where the timer would be inadequate.
-
- A program using the timer for a big job must continually reenter and leave
- the processing loop with every WM_TIMER message. This is easy to structure
- when a single loop is involved, but it becomes a nightmare for more complex
- jobs with lots of nested loops.
-
- The timer also slows down the big job. It is not possible to receive
- WM_TIMER messages at a rate faster than that of the hardware clock. Under
- OS/2, this means the program receives a WM_TIMER message only once every
- 31.25 milliseconds. But BIGJOB2 spends less than 20 milliseconds (on an 8-
- MHz IBM PC/AT) processing each WM_TIMER message. Because the calculation is
- paced by the timer, the calculation won't finish any faster on a 20-MHz
- 80386 machine.
-
- Clearly, as a general solution to the big job program, the timer approach
- must be rejected.
-
-
- The Peek Message
-
- Another solution that is very common in Windows is the use of the
- PeekMessage function (called WinPeekMsg in the Presentation Manager). The
- PeekMessage function is very similar to GetMessage. Under Windows, the
- PeekMessage function first checks if there are messages waiting in the
- program's message queue. If so, PeekMessage fills the message structure
- with the next message from the queue and returns a non-zero value.
-
- If the message queue of the program calling PeekMessage is empty, then
- Windows switches to a program with a nonempty message queue to allow that
- program to process its own messages. (This is often called "yielding" to
- other programs.) When no messages remain in any program's message queues,
- then PeekMessage returns control to the original program and returns a zero
- value.
-
- PeekMessage allows all Windows programs to process outstanding messages
- but then returns control to the program calling PeekMessage after all this
- message processing is completed. Using PeekMessage allows a program to do a
- big job while periodically allowing itself and other programs to process
- their messages. Several Windows programs, including SPOOLER and TERMINAL,
- use PeekMessage in this way in order to simulate multitasking. Programs
- that print frequently use PeekMessage to allow a user to cancel a print job
- from a dialog box.
-
- The WinPeekMsg function in the Presentation Manager is very similar to
- PeekMessage. However, in the Presentation Manager, WinPeekMsg is used by a
- program to process its own messages rather than to yield to other programs.
- Multitasking occurs normally in the Presentation Manager if a program does
- not clog up the processing of keyboard and mouse input. The WinPeekMsg
- function provides a good solution to the big job problem. BIGJOB3 in
- Figure 4 shows how this is done.
-
- Like BIGJOB2, BIGJOB3 does the full calculation in response to a WM_COMMAND
- message. However, within the calculation loop, BIGJOB3 calls WinPeekMsg to
- determine if any messages are in its message queue. If so, it removes them
- with WinGetMsg and dispatches them to a window procedure with
- WinDispatchMsg, just as in the normal message loop in main.
-
- One of these messages could be another WM_COMMAND message generated when the
- user selects Abort from the menu. BIGJOB3 uses the bContinueCalc variable to
- indicate when the calculation has been aborted.
-
- You'll notice that some special processing is required for the WM_QUIT
- message. This message is posted to the message queue by the Presentation
- Manager as a default response when the user selects Close from the system
- menu. The WM_QUIT message should not be removed from the queue within the
- window procedure. Instead, BIGJOB3 exits the window procedure so that the
- WM_QUIT message can be retrieved from the message queue in the main
- function.
-
- Although message peeking usually works well in Windows and Presentation
- Manager programs, it's always a little messy in practice. The PeekMessage
- and WinPeekMsg functions must be called frequently enough to give the
- system a good response time, yet there's an inordinate amount of code
- involved. If the big job must be aborted, it's sometimes difficult to get
- out of a calculation loop in a structured manner. In short, code involving
- WinPeekMsg often looks like a kludge.
-
-
- A Second Thread
-
- The solutions in BIGJOB2 and BIGJOB3 are applicable in both Windows and the
- Presentation Manager. Now let's do something that is impossible in Windows
- but a natural in the Presentation Manager: create a second thread of
- execution to do the big job.
-
- When a program contains multiple threads of execution, the multiple threads
- run concurrently. All threads in a process share the program's
- resources──such as open files, memory, semaphores, and queues──but each
- thread has its own CPU state, dispatching priority, and stack.
-
- Within a program, a second thread of execution looks like a function. All
- local automatic variables in the thread function are private to the thread
- because they are stored on the thread's stack. Local static variables in the
- thread function can be shared by all threads based on that same function.
-
- When programming for the Presentation Manager, threads fall into two
- categories: a thread is either a "message queue thread" or a "nonmessage
- queue thread." A thread becomes a message queue thread when it calls
- WinCreateMsgQueue. It reverts back to being a nonmessage queue thread when
- it calls WinDestroyMsgQueue.
-
- A Presentation Manager program always creates a message queue in at least
- one thread. A thread must create a message queue before it can create
- windows. The message queue is used to store messages for all of the windows
- created in the thread. However, other threads in a Presentation Manager
- program do not need to create message queues if they do not create
- windows.
-
-
- Thread Restrictions
-
- In a Presentation Manager program, nonmessage queue threads have some
- advantages over message queue threads──and some disadvantages.
-
- The good news is that a nonmessage queue thread is not bound by the 1/10
- second rule. Because the thread does not receive or process messages, it
- needn't worry about clogging up the processing of messages in message queue
- threads. Thus, a nonmessage queue thread is often ideal for doing big jobs.
-
- The bad news is that nonmessage queue threads are restricted in the
- Presentation Manager functions they may call. Basically, a thread without a
- message queue cannot create windows, cannot send a message to a window
- procedure in a message queue thread, and cannot call any function that
- causes a message to be sent to a window procedure.
-
- Some of these restrictions are obvious: a nonmessage queue thread cannot
- create a window because it has no queue to store messages to that window.
- However, a nonmessage queue thread can call some functions that affect
- windows created in message queue threads. For example, a nonmessage queue
- thread can obtain a presentation space handle for a window created in a
- message queue thread and can paint something on the surface of that window.
-
- Nonmessage queue threads cannot send messages to message queue threads. The
- WinSendMsg function is not allowed. Nor can they call functions that send
- messages. For example, WinDestroyWindow cannot be called from a nonmessage
- queue thread because it sends a window procedure a WM_DESTROY message. The
- functions that are forbidden to nonmessage queue threads are listed in the
- Presentation Manager documentation.
-
- Although a nonmessage queue thread cannot send a message using WinSendMsg,
- the thread can post a message by calling WinPostMsg. This latter function
- places the message in a thread's message queue and returns immediately.
-
-
- Semaphores
-
- Threads within a single process must often communicate with each other in
- various ways. The execution of threads must be coordinated so that the
- threads don't step on each other's toes. This requires some handshaking.
- Often, threads must also signal each other and pass data among themselves.
-
- A message queue thread generally communicates with a nonmessage queue
- thread by using semaphores. A nonmessage queue thread communicates with
- a message queue thread by using posted messages. Both threads can also
- access common global variables.
-
- The BIGJOB4 program in Figure 5, which uses a nonmessage queue thread to do
- a big job, shows this signaling and communication.
-
- The global variable lSemTrigger is a RAM semaphore. This semaphore is set
- during the WM_CREATE message in ClientWndProc. The window procedure then
- creates a second thread by calling the OS/2 DosCreateThread function using
- SecondThread as the thread function.
-
- SecondThread waits for lSemTrigger to be cleared. ClientWndProc clears the
- semaphore when the user selects Start from BIGJOB4's menu. SecondThread then
- does the calculation. When it's finished, it posts a WM_CALC_DONE message to
- the window procedure. (This is a "user-defined" message defined within
- BIGJOB4.C.) The elapsed time of the calculation is passed to the window
- procedure in the lParam1 variable that accompanies this message.
-
-
- Aborting the Calculation
-
- BIGJOB4 also allows the user to abort the calculation. When Abort is
- selected from the menu, the window procedure sets the bContinueCalc global
- variable to FALSE and disables the Abort menu option.
-
- SecondThread checks this bContinueCalc variable during the calculation and
- breaks from the loop when the variable is no longer non-zero. The thread
- then sets the semaphore and posts a WM_CALC_ABORTED to the window. On
- receipt of this message, the client window enables the Start menu option
- again. The semaphore is already set, so SecondThread can't proceed with a
- new calculation until Start is chosen again. This is an example of some
- simple handshaking between the two threads.
-
- Note that the semaphore is used only for blocking and unblocking the
- nonmessage queue thread. A message queue thread should not be made to wait
- on a semaphore because that may violate the 1/10 second rule. If absolutely
- necessary, a nonmessage queue thread could suspend a message queue thread
- for very short periods of time by calling DosSuspendThread or
- DosEnterCritSec. This is sometimes helpful when both threads access global
- variables. (It's not necessary in BIGJOB4 when the threads access
- bContinueCalc because this variable is altered or accessed in one machine
- code instruction.)
-
- The message queue thread in the BIGJOB4 program calls DosSuspendThread to
- suspend SecondThread after exiting the message loop in main before
- destroying the window and message queue. SecondThread could be in the middle
- of a calculation at this time. Suspending the thread prevents it from
- posting a message to the message queue after the message queue is
- destroyed.
-
-
- Thinking Threads
-
- A nonmessage queue thread is nearly essential in Presentation Manager
- programs that must read input other than the keyboard and the mouse. An
- obvious example of this is a communications program. Such a program would
- have a client window in the message queue thread that processes keyboard
- messages, writes the characters to the communications port using DosWrite,
- and (if local echo is in effect) also writes the characters to the surface
- of the window.
-
- The nonmessage queue thread reads the communications port with the DosRead
- function. Used most efficiently, this function does not return unless a
- character has been read from the serial port. A message queue thread should
- not call DosRead to get input from the serial port because it may violate
- the 1/10 second rule. When the nonmessage queue thread reads a character, it
- can post a user-defined message to the window. The client window processes
- the message by displaying the character to the window.
-
- A Presentation Manager program using queues for interprocess communication
- should also create a nonmessage queue thread for reading the queue. The
- nonmessage queue thread calls the DosReadQueue function with the "no wait"
- flag set to 0, thus blocking the thread until there is something in the
- queue.
-
- In BIGJOB4, the second thread is created once and unblocked whenever it has
- to do the big job. If a program has numerous big jobs handled in lots of
- different thread functions, it would be best not to create all these
- threads initially, but to create a thread each time it is needed. When a
- thread is finished with its big job, it can post a message to the client
- window and terminate using the DosExit call with a first parameter of 0.
-
-
- Object Windows
-
- BIGJOB4 shows how a program can handle big jobs in a nonmessage queue
- thread. A Presentation Manager program can also do big jobs in a second
- message queue thread that creates "object windows."
-
- Object windows are created in a message queue thread just like normal
- windows. They have a window procedure that processes messages to the object
- window, again just like normal windows. However, object windows do not
- appear on the screen and do not get keyboard and mouse input. In fact,
- object windows basically receive only two messages: WM_CREATE and
- WM_DESTROY. The WM_CREATE message is sent during the WinCreateWindow call
- that creates the object window, and the WM_DESTROY message is sent during
- the WinDestroyWindow call that destroys the window.
-
- (In the pre-release version of the Presentation Manager I have, object
- windows also receive a WM_ADJUSTWINDOWPOS message concurrently with the
- WinCreateWindow call.)
-
- You normally use object windows specifically to receive messages from other
- windows, perhaps to implement an object-oriented programming language. But
- object windows also help out in processing big jobs. If a thread creates
- only object windows, then there are only a very limited number of messages
- the window procedure can receive. This greatly simplifies the processing
- of these messages in the object window's window procedure.
-
- There are several advantages to using object windows for big jobs. The
- object window is not restricted to a subset of Presentation Manager
- functions. You can thus use messages for all communications between the
- client window and the object window. If you've come to regard message-driven
- architecture as inherently superior to traditional top-down architecture,
- you may prefer this. The use of object windows in separate threads is more
- in tune with the architecture of the Presentation Manager than are simple
- nonmessage queue threads.
-
- The use of an object window in a second message queue thread is shown in the
- BIGJOB5 program in Figure 6. The BIGJOB5.C file defines six messages that
- the client window and object window use for communicating with each other.
-
- BIGJOB5 generally creates the second thread after creating the program's
- main window. BIGJOB5's SecondThread function looks a lot like the main
- function. Although it does not call WinInitialize, it registers a window
- class and creates a window. The WinCreateWindow call to create an object
- window is very simple: the parent window parameter is set to HWND_OBJECT,
- the second parameter is set to the name of the window class, and all other
- parameters are set to 0 or NULL.
-
- SecondThread then sends a WM_OBJECT_CREATED message to ClientWndProc to
- notify it that the object window has been created and is ready for work.
- SecondThread then enters a normal message loop.
-
- When you select Start from BIGJOB5's menu, the client window posts the
- message WM_START_CALC to the object window. Similarly, selecting Abort
- from BIGJOB5's menu causes the client window to post a WM_ABORT_CALC
- message. Note as well that ClientWndProc processes the normal WM_CLOSE
- message (generated by selecting Close from the system menu) by posting a
- WM_QUIT message to the object window.
-
- ObjectWndProc begins the big job in response to the WM_START_CALC message.
- While doing the calculation, it can be interrupted only by one of two
- possible messages posted to its message queue. These messages are
- WM_ABORT_CALC and WM_QUIT. In either case, the calculation can be
- unconditionally aborted.
-
- In the calculation loop, ObjectWndProc calls the WinQueryQueueStatus
- function and checks the QS_POSTMSG bit of the return value. (It should be
- necessary only to check that the return value is non-zero, but in the
- version of the Presentation Manager I used, this function was a little
- buggy.) This check of WinQueryQueueStatus is almost as simple as checking
- bContinueCalc in BIGJOB4, but not nearly as complex as the code involved
- with using WinPeekMsg in BIGJOB3.
-
- ObjectWndProc notifies ClientWndProc that it has finished or aborted a
- calculation by posting the messages WM_CALC_DONE and WM_CALC_ABORTED
- respectively. ClientWndProc uses these messages to enable its menu for
- further calculations.
-
- Ending the program in an orderly manner (that is, where both main and
- SecondThread destroy their own windows and message queues) is a little
- tricky. ClientWndProc processes a WM_CLOSE message by posting a WM_QUIT
- message to the object window. This causes ObjectWndProc to abort the big job
- if it's doing one at the time. When this WM_QUIT message is retrieved
- from SecondThread's message queue, WinGetMsg returns 0. SecondThread
- destroys its window, its message queue, and posts a WM_OBJECT_DESTROYED
- message to ClientWndProc. On receipt of this message, ClientWndProc posts
- itself a WM_QUIT message and terminates normally.
-
- The use of an object window for big jobs is certainly more complex than the
- use of a nonmessage queue thread, so you may not like it. Moreover, the
- object window approach is not good for some big jobs. For example, when a
- second thread must use DosRead to get input from a communications port or
- DosReadQueue to get input from a queue, the thread is blocked within the
- OS/2 function and obviously cannot check the message queue status using
- WinQueryQueueStatus.
-
-
- No More "Please Wait"
-
- We started out looking at BIGJOB1, a program that did the job it was meant
- to do, but did it in a way that was not advantageous for the user. Our
- immediate rejection of this program and our search for better ways of doing
- big jobs indicates some major changes in our conception of proper behavior
- in application programs.
-
- In a traditional single-tasking nonwindowed environment, of course you have
- to wait while the database program is sorting a file. When you start a file
- sort, it's time to take a coffee break.
-
- In a traditional multitasking operating environment, you may be able to let
- the database program sort in the background, and you can work on another
- program while waiting for the sort to finish.
-
- In a multitasking window environment like the Presentation Manager,
- however, we are satisfied only when the user can continue to interact with a
- program even when it's doing a big job. Obviously, the complexities
- involved with structuring a program in this way require some extra work on
- the part of the programmer. But that makes the program better to use.
-
- Just as we can no longer tolerate programs that require the user to memorize
- scores of commands, we can no longer tolerate programs that throw up a
- "Please Wait" message and require a user to wait until the program finishes
- its big job.
-
-
- Figure 1:
-
- BIGJOB.RC resource script file
-
- #include <os2.h>
- #include "bigjob.h"
-
- MENU ID_RESOURCE
- {
- SUBMENU "~Repetitions", IDM_REPS
- {
- MENUITEM " 10", IDM_10, MIA_CHECKED
- MENUITEM " 100", IDM_100
- MENUITEM " 1000", IDM_1000
- MENUITEM " 10000", IDM_10000
- }
- SUBMENU "~Action", IDM_ACTION
- {
- MENUITEM "~Start", IDM_START
- MENUITEM "~Abort", IDM_ABORT, MIA_DISABLED
- }
- }
-
- BIGJOB.H header file
-
- #define ID_RESOURCE 1
-
- #define IDM_REPS 1
- #define IDM_ACTION 2
-
- #define IDM_10 10
- #define IDM_100 100
- #define IDM_1000 1000
- #define IDM_10000 10000
-
- #define IDM_START 20
- #define IDM_ABORT 21
-
- /* Definitions, functions, and variables for BIGJOBx.C */
-
- #ifndef RC_INVOKED /* This stuff not needed for .RC file */
-
- #define STATUS_READY 0
- #define STATUS_WORKING 1
- #define STATUS_DONE 2
-
- ULONG EXPENTRY ClientWndProc (HWND, USHORT, ULONG, ULONG) ;
-
- HAB hab ;
-
- double Savage (double A)
- {
- return tan (atan (exp (log (sqrt (A * A))))) + 1.0 ;
- }
-
- VOID CheckMenuItem (HWND hwnd, SHORT iMenuItem, BOOL bCheck)
- {
- HWND hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
- HWND hwndMenu = WinWindowFromID (hwndParent, FID_MENU) ;
-
- WinSendMsg (hwndMenu, MM_SETITEMATTR, MAKEULONG (iMenuItem, TRUE),
- MAKEULONG (MIA_CHECKED, bCheck ? MIA_CHECKED : 0)) ;
- }
-
- VOID EnableMenuItem (HWND hwnd, SHORT iMenuItem, BOOL bEnable)
- {
- HWND hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
- HWND hwndMenu = WinWindowFromID (hwndParent, FID_MENU) ;
-
- WinSendMsg (hwndMenu, MM_SETITEMATTR, MAKEULONG (iMenuItem, TRUE),
- MAKEULONG (MIA_DISABLED, bEnable ? 0 : MIA_DISABLED)) ;
- }
-
- VOID PaintWindow (HWND hwnd, SHORT iStatus, SHORT iRep, LONG lTime)
- {
- static CHAR *szMessage [3] = { "Ready", "Working ...",
- "%d repetitions in %ld msec." } ;
- CHAR szBuffer [60] ;
- HPS hps ;
- WRECT wrc ;
-
- hps = WinBeginPaint (hwnd, NULL, NULL) ;
- GpiErase (hps) ;
-
- WinQueryWindowRect (hwnd, &wrc) ;
-
- sprintf (szBuffer, szMessage [iStatus], iRep, lTime) ;
-
- WinDrawText (hps, -1, szBuffer, &wrc, DT_CENTER | DT_VCENTER) ;
- WinEndPaint (hps) ;
- }
-
- #endif
-
-
- Figure 2:
-
- BIGJOB1 Make File
-
- bigjob1.obj : bigjob1.c bigjob.h
- cl -c -FPa -G2sw -W3 -Zp bigjob1.c
-
- bigjob.res : bigjob.rc bigjob.h
- rc -r bigjob.rc
-
- bigjob1.exe : bigjob1.obj bigjob1.def bigjob.res
- link bigjob1, /align:16, /map, /nod slibc slibcp slibfa os2,
- bigjob1 rc bigjob.res bigjob1.exe
-
-
- BIGJOB1.DEF Module Definition File
-
- NAME BIGJOB1
- DESCRIPTION 'BIGJOB Demo Program No. 1(C) Charles Petzold, 1988'
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- BIGJOB1.C - Naive Approach to Lengthy Processing Job
-
- #define INCL_WIN
-
- #include <os2.h>
- #include <math.h>
- #include <stdio.h>
- #include "bigjob.h"
-
- INT main (VOID)
- {
- static CHAR szClassName [] = "BigJob1" ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ClientWndProc,
- CS_SYNCPAINT | CS_SIZEREDRAW, 0, NULL) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX
- | FS_MENU,
- szClassName, "BIGJOB Demo No. 1",
- 0L, NULL, ID_RESOURCE, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
- ULONG mp2)
- {
- static SHORT iCalcRep, iCurrentRep = IDM_10 ;
- static SHORT iStatus = STATUS_READY ;
- static ULONG lElapsedTime ;
- double A ;
- SHORT i ;
-
- switch (msg)
- {
- case WM_COMMAND:
-
- switch (LOUSHORT (mp1))
- {
- case IDM_10:
- case IDM_100:
- case IDM_1000:
- case IDM_10000:
- CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
- iCurrentRep = LOUSHORT (mp1) ;
- CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
- break ;
-
- case IDM_START:
- EnableMenuItem (hwnd, IDM_START, FALSE) ;
- EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
-
- iStatus = STATUS_WORKING ;
- WinInvalidateRect (hwnd, NULL) ;
-
- iCalcRep = iCurrentRep ;
- lElapsedTime = WinGetCurrentTime (hab) ;
-
- for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
- A = Savage (A) ;
-
- lElapsedTime = WinGetCurrentTime (hab) -
- lElapsedTime ;
-
- iStatus = STATUS_DONE ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- case IDM_ABORT:
-
- /* Not much we can do here */
-
- break ;
-
- default:
- break ;
- }
- break ;
-
- case WM_PAINT:
- PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
-
- Figure 3:
-
- BIGJOB2 Make File
-
- bigjob2.obj : bigjob2.c bigjob.h
- cl -c -FPa -G2sw -W3 -Zp bigjob2.c
-
- bigjob.res : bigjob.rc bigjob.h
- rc -r bigjob.rc
-
- bigjob2.exe : bigjob2.obj bigjob2.def bigjob.res
- link bigjob2, /align:16, /map, /nod slibc slibcp slibfa os2,
- bigjob2 rc bigjob.res bigjob2.exe
-
- BIGJOB2.DEF Module Definition File
-
- NAME BIGJOB2
- DESCRIPTION 'BIGJOB Demo Program No. 2(C) Charles Petzold, 1988'
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- BIGJOB2.C - Timer Approach to Lengthy Processing Job
-
- #define INCL_WIN
-
- #include <os2.h>
- #include <math.h>
- #include <stdio.h>
- #include "bigjob.h"
-
- #define ID_TIMER 1
-
- INT main (VOID)
- {
- static CHAR szClassName [] = "BigJob2" ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ClientWndProc,
- CS_SYNCPAINT | CS_SIZEREDRAW, 0, NULL) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX
- | FS_MENU,
- szClassName, "BigJob Demo No. 2",
- 0L, NULL, ID_RESOURCE, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mpl,
- ULONG mp2)
- {
- static double A ;
- static SHORT i, iCalcRep, iCurrentRep = IDM_10 ;
- static SHORT iStatus = STATUS_READY ;
- static ULONG lElapsedTime;
-
- switch (msg)
- {
- case WM_COMMAND:
-
- switch (LOUSHORT (mp1))
- {
- case IDM_10:
- case IDM_100:
- case IDM_1000:
- case IDM_10000:
- CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
- iCurrentRep = LOUSHORT (mp1) ;
- CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
- break ;
-
- case IDM_START:
-
- if (!WinStartTimer (hab, hwnd, ID_TIMER, 1))
- {
- WinAlarm (HWND_DESKTOP, WA_ERROR) ;
- break ;
- }
-
- EnableMenuItem (hwnd, IDM_START, FALSE) ;
- EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
-
- iStatus = STATUS_WORKING ;
- WinInvalidateRect (hwnd, NULL) ;
-
- iCalcRep = iCurrentRep ;
- lElapsedTime = WinGetCurrentTime (hab) ;
-
- A = 1.0 ;
- i = 0 ;
-
- break ;
-
- case IDM_ABORT:
- WinStopTimer (hab, hwnd, ID_TIMER) ;
-
- iStatus = STATUS_READY ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- default:
- break ;
- }
- break ;
-
- case WM_TIMER:
-
- A = Savage (A) ;
-
- if (++i == iCalcRep)
- {
- lElapsedTime = WinGetCurrentTime (hab) -
- lElapsedTime ;
-
- WinStopTimer (hab, hwnd, ID_TIMER) ;
-
- iStatus = STATUS_DONE ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- }
- break ;
-
- case WM_PAINT:
- PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
-
- Figure 4:
-
- BIGJOB3 Make File
-
- bigjob3.obj : bigjob3.c bigjob.h
- cl -c -FPa -G2sw -W3 -Zp bigjob3.c
-
- bigjob.res : bigjob.rc bigjob.h
- rc -r bigjob.rc
-
- bigjob3.exe : bigjob3.obj bigjob3.def bigjob.res
- link bigjob3, /align:16, /map, /nod slibc slibcp slibfa os2,
- bigjob3 rc bigjob.res bigjob3.exe
-
- BIGJOB3.DEF Module Definition File
-
- NAME BIGJOB3
- DESCRIPTION 'BigJob Demo Program No. 3(C) Charles Petzold, 1988'
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- BIGJOB3.C - PeekMessage Approach to Lengthy Processing Job
-
- #define INCL_WIN
-
- #include <os2.h>
- #include <math.h>
- #include <stdio.h>
- #include "bigjob.h"
-
- INT main (VOID)
- {
- static CHAR szClassName [] = "BigJob3" ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ClientWndProc,
- CS_SIZEREDRAW, 0, NULL) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX
- | FS_MENU,
- szClassName, "BigJob Demo No. 3",
- 0L, NULL, ID_RESOURCE, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg,
- ULONG mp1,ULONG mp2)
- {
- static BOOL bContinueCalc = FALSE ;
- static SHORT iStatus = STATUS_READY ;
- static SHORT iCalcRep, iCurrentRep = IDM_10 ;
- static ULONG lElapsedTime ;
- double A ;
- SHORT i ;
- QMSG qmsg ;
-
-
- switch (msg)
- {
- case WM_COMMAND:
-
-
- switch (LOUSHORT (mp1))
- {
- case IDM_10:
- case IDM_100:
- case IDM_1000:
- case IDM_10000:
- CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
- iCurrentRep = LOUSHORT (mp1) ;
- CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
- break ;
-
- case IDM_START:
- EnableMenuItem (hwnd, IDM_START, FALSE) ;
- EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
-
- iStatus = STATUS_WORKING ;
- WinInvalidateRect (hwnd, NULL) ;
-
- iCalcRep = iCurrentRep ;
- bContinueCalc = TRUE ;
- lElapsedTime = WinGetCurrentTime (hab) ;
-
- qmsg.msg = WM_NULL ;
-
- for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
- {
- A = Savage (A) ;
-
- while (WinPeekMsg (hab, &qmsg, NULL, 0, 0,
- PM_NOREMOVE))
- {
- if (qmsg.msg == WM_QUIT)
- break ;
-
- WinGetMsg (hab, &qmsg, NULL, 0, 0) ;
- WinDispatchMsg (hab, &qmsg) ;
-
- if (!bContinueCalc)
- break ;
- }
-
- if (!bContinueCalc || qmsg.msg == WM_QUIT)
- break ;
- }
- lElapsedTime = WinGetCurrentTime (hab) -
- lElapsedTime ;
-
- if (!bContinueCalc || qmsg.msg == WM_QUIT)
- iStatus = STATUS_READY ;
- else
- iStatus = STATUS_DONE ;
-
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- case IDM_ABORT:
- bContinueCalc = FALSE ;
- break ;
-
- default:
- break ;
- }
- break ;
-
- case WM_PAINT:
- PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
-
- Figure 5:
-
- BIGJOB4 Make File
-
- bigjob4.obj : bigjob4.c bigjob.h
- cl -c -FPa -G2sw -W3 -Zp bigjob4.c
-
- bigjob.res : bigjob.rc bigjob.h
- rc -r bigjob.rc
-
- bigjob4.exe : bigjob4.obj bigjob4.def bigjob.res
- link bigjob4, /align:16, /map, /nod slibc slibcp slibfa os2,
- bigjob4 rc bigjob.res bigjob4.exe
-
- BIGJOB4.DEF Module Definition File
-
- NAME BIGJOB4
- DESCRIPTION 'BigJob Demo Program No. 4(C) Charles Petzold, 1988'
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- BIGJOB4.DEF ── Second Thread Approach to Lengthy Processing Job
-
- #define INCL_WIN
- #define INCL_DOS
-
- #include <os2.h>
- #include <math.h>
- #include <stdio.h>
- #include "bigjob.h"
-
- #define WM_CALC_DONE (WM_USER + 0)
- #define WM_CALC_ABORTED (WM_USER + 1)
-
- VOID FAR SecondThread (VOID) ;
-
- BOOL bContinueCalc = FALSE ;
- HWND hwndClient ;
- SHORT iCalcRep ;
- LONG lSemTrigger ;
- TID idThread ;
- UCHAR cThreadStack [4096] ;
-
- INT main (VOID)
- {
- static CHAR szClassName [] = "BigJob4" ;
- HMQ hmq ;
- HWND hwndFrame ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ClientWndProc,
- CS_SIZEREDRAW, 0, NULL) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX
- | FS_MENU,
- szClassName, "BigJob Demo No. 4",
- 0L, NULL, ID_RESOURCE, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- DosSuspendThread (idThread) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
- ULONG mp2)
- {
- static SHORT iCurrentRep = IDM_10 ;
- static SHORT iStatus = STATUS_READY ;
- static ULONG lElapsedTime ;
-
- switch (msg)
- {
- case WM_CREATE:
-
- DosSemSet (&lSemTrigger) ;
-
- if (DosCreateThread (SecondThread, &idThread,
- cThreadStack + sizeof
- cThreadStack))
-
- WinAlarm (HWND_DESKTOP, WA_ERROR) ;
-
- break ;
-
- case WM_COMMAND:
-
- switch (LOUSHORT (mp1))
- {
- case IDM_10:
- case IDM_100:
- case IDM_1000:
- case IDM_10000:
- CheckMenuItem (hwnd, iCurrentRep,
- FALSE) ;
- iCurrentRep = LOUSHORT (mp1) ;
- CheckMenuItem (hwnd, iCurrentRep,
- TRUE) ;
- break ;
-
- case IDM_START:
- iStatus = STATUS_WORKING ;
- WinInvalidateRect (hwnd, NULL) ;
-
- iCalcRep = iCurrentRep ;
- bContinueCalc = TRUE ;
- DosSemClear (&lSemTrigger) ;
-
- EnableMenuItem (hwnd, IDM_START,
- FALSE) ;
- EnableMenuItem (hwnd, IDM_ABORT,
- TRUE);
- break ;
-
- case IDM_ABORT:
- bContinueCalc = FALSE ;
-
- EnableMenuItem (hwnd, IDM_ABORT,
- FALSE) ;
- break ;
-
- default:
- break ;
- }
- break ;
-
- case WM_CALC_DONE:
-
- iStatus = STATUS_DONE ;
- lElapsedTime = mp1 ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- case WM_CALC_ABORTED:
-
- iStatus = STATUS_READY ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- break ;
-
- case WM_PAINT:
- PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
- VOID FAR SecondThread ()
- {
- double A ;
- int i ;
- LONG lTime ;
-
- while (1)
- {
- DosSemWait (&lSemTrigger, -1L) ;
-
- lTime = WinGetCurrentTime (hab) ;
-
- for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
- {
- if (!bContinueCalc)
- break ;
-
- A = Savage (A) ;
- }
-
- lTime = WinGetCurrentTime (hab) - lTime ;
-
- DosSemSet (&lSemTrigger) ;
-
- if (bContinueCalc)
- WinPostMsg (hwndClient, WM_CALC_DONE, lTime, 0L) ;
- else
- WinPostMsg (hwndClient, WM_CALC_ABORTED, 0L, 0L) ;
- }
- }
-
-
- Figure 6:
-
- BIGJOB5 Make File
-
- bigjob5.obj : bigjob5.c bigjob.h
- cl -c -FPa -G2sw -W3 -Zp bigjob5.c
-
- bigjob.res : bigjob.rc bigjob.h
- rc -r bigjob.rc
-
- bigjob5.exe : bigjob5.obj bigjob5.def bigjob.res
- link bigjob5, /align:16, /map, /nod slibc slibcp slibfa os2,
- bigjob5 rc bigjob.res bigjob5.exe
-
- BIGJOB5.DEF Module Definition File
-
- NAME BIGJOB5
- DESCRIPTION 'BigJob Demo Program No. 5(C) Charles Petzold, 1988'
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
- ObjectWndProc
-
- BIGJOB5.C - Object Window Approach to Lengthy Processing Job
-
- #define INCL_WIN
- #define INCL_DOS
-
- #include <os2.h>
- #include <math.h>
- #include <stdio.h>
- #include "bigjob.h"
-
- #define WM_OBJECT_CREATED (WM_USER + 0)
- #define WM_START_CALC (WM_USER + 1)
- #define WM_ABORT_CALC (WM_USER + 2)
- #define WM_CALC_DONE (WM_USER + 3)
- #define WM_CALC_ABORTED (WM_USER + 4)
- #define WM_OBJECT_DESTROYED (WM_USER + 5)
-
- VOID FAR SecondThread (VOID) ;
- ULONG EXPENTRY ObjectWndProc (HWND, USHORT, ULONG, ULONG) ;
-
- HWND hwndClient, hwndObject ;
- UCHAR cThreadStack [8192] ;
-
- INT main (VOID)
- {
- static CHAR szClassName [] = "BigJob5" ;
- HMQ hmq ;
- HWND hwndFrame ;
- QMSG qmsg ;
- TID idThread ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ClientWndProc,
- CS_SIZEREDRAW, 0, NULL) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX
- | FS_MENU,
- szClassName, "BigJob Demo No. 5",
- 0L, NULL, ID_RESOURCE, &hwndClient) ;
-
- EnableMenuItem (hwndClient, IDM_START, FALSE) ;
-
- if (DosCreateThread (SecondThread, &idThread,
- cThreadStack + sizeof cThreadStack))
-
- WinAlarm (HWND_DESKTOP, WA_ERROR) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
- ULONG mp2)
- {
- static SHORT iCalcRep, iCurrentRep = IDM_10 ;
- static SHORT iStatus = STATUS_READY ;
- static ULONG lElapsedTime ;
-
- switch (msg)
- {
- case WM_OBJECT_CREATED:
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- break ;
-
- case WM_COMMAND:
-
- switch (LOUSHORT (mp1))
- {
- case IDM_10:
- case IDM_100:
- case IDM_1000:
- case IDM_10000:
- CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
- iCurrentRep = LOUSHORT (mp1) ;
- CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
- break ;
-
- case IDM_START:
- EnableMenuItem (hwnd, IDM_START, FALSE) ;
- EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
-
- iStatus = STATUS_WORKING ;
- WinInvalidateRect (hwnd, NULL) ;
-
- iCalcRep = iCurrentRep ;
- WinPostMsg (hwndObject, WM_START_CALC,
- MAKEULONG (iCalcRep, 0), 0L) ;
- break ;
-
- case IDM_ABORT:
- WinPostMsg (hwndObject, WM_ABORT_CALC,
- 0L, 0L) ;
-
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- default:
- break ;
- }
- break ;
-
- case WM_CALC_DONE:
-
- iStatus = STATUS_DONE ;
- lElapsedTime = mp1 ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
- break ;
-
- case WM_CALC_ABORTED:
-
- iStatus = STATUS_READY ;
- WinInvalidateRect (hwnd, NULL) ;
-
- EnableMenuItem (hwnd, IDM_START, TRUE) ;
- break ;
-
- case WM_PAINT:
-
- PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
- break ;
-
- case WM_CLOSE:
-
- if (hwndObject)
- WinPostMsg (hwndObject, WM_QUIT, 0L, 0L) ;
- else
- WinPostMsg (hwnd, WM_QUIT, 0L, 0L) ;
- break ;
-
- case WM_OBJECT_DESTROYED:
-
- WinPostMsg (hwnd, WM_QUIT, 0L, 0L) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
- VOID FAR SecondThread ()
- {
- static CHAR szClassName [] = "BigJob5.Object" ;
- HMQ hmq ;
- QMSG qmsg ;
-
- hmq = WinCreateMsgQueue (hab, 0) ;
-
- WinRegisterClass (hab, szClassName, ObjectWndProc, 0L, 0, NULL) ;
-
- hwndObject = WinCreateWindow (HWND_OBJECT, szClassName,
- NULL, 0L, 0, 0, 0, 0, NULL, NULL, 0, NULL, NULL) ;
-
- WinPostMsg (hwndClient, WM_OBJECT_CREATED, 0L, 0L) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndObject) ;
- WinDestroyMsgQueue (hmq) ;
-
- WinPostMsg (hwndClient, WM_OBJECT_DESTROYED, 0L, 0L) ;
-
- DosExit (0, 0) ;
- }
-
- ULONG EXPENTRY ObjectWndProc (HWND hwnd, USHORT msg, ULONG mp1,
- ULONG mp2)
- {
- double A ;
- SHORT i, iCalcRep ;
- LONG lQueueStatus, lTime ;
-
- switch (msg)
- {
- case WM_START_CALC:
-
- iCalcRep = LOUSHORT (mp1) ;
- lTime = WinGetCurrentTime (hab) ;
-
- for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
- {
- lQueueStatus = WinQueryQueueStatus (HWND_DESKTOP) ;
-
- if (lQueueStatus & QS_POSTMSG)
- break ;
-
- A = Savage (A) ;
- }
-
- if (lQueueStatus & QS_POSTMSG)
- break ;
-
- lTime = WinGetCurrentTime (hab) - lTime ;
-
- WinPostMsg (hwndClient, WM_CALC_DONE, lTime, 0L) ;
- break ;
-
- case WM_ABORT_CALC:
-
- WinPostMsg (hwndClient, WM_CALC_ABORTED, 0L, 0L) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return 0L ;
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- OS/2 LAN Manager Provides a Platform for Server-Based Network Applications
-
- Alan Kesler
-
- The introduction of personal computer──based networks has provided
- workgroups with systems that are capable of sharing expensive peripherals
- and supporting applications. This article provides a historical perspective
- of application development on PC network platforms, examines the impact of
- the OS/2 systems on PC network applications, and introduces the programmatic
- interfaces and capabilities provided by the Microsoft(R) OS/2 LAN Manager.
-
-
- Historical Perspective
-
- Applications that operate on PC networks can be categorized in one of
- three ways. Standalone applications see the network as nothing more than a
- shared printer and shared disk. They could just as easily be used on a
- standalone personal computer. Network aware applications recognize the
- existence of the network and use some of the network communication and
- data-sharing capabilities. Multiuser databases like dBASE III PLUS(TM) or
- Paradox(R) are the best examples of applications that are network aware.
- Many network aware applications may also be used in a standalone
- configuration.
-
- Finally, there are network intrinsic applications. These
- applications──electronic mail, multiuser document editing, and distributed
- database applications──don't make sense in a single personal computer or a
- standalone configuration. What sets these applications apart from the
- standalone and network aware applications is their use of distributed
- computing on the PC LAN; with standalone and network aware applications,
- all computing occurs in the network workstation. Only the program file and
- data are downloaded from the network server.
-
- This style of network-based computing has some disadvantages, such as
- underutilization of computing power sitting idle elsewhere on the
- network. Moreover, data-intensive applications will suffer performance
- problems when all computing occurs in the workstation. With back-end
- database servers, applications designed on a server and requester model
- can offer much greater performance to many more users. Finally,
- security is threatened by database applications that place large amounts
- of unnecessary data on the network, since all data must be sent to the
- network workstation in order to be processed. With distributed
- applications, user permission and data access can be verified at the
- server while a request is being processed. Only the requested information
- need be placed on the network and sent to the requesting workstation.
-
- Network intrinsic applications are set apart from both standalone and
- network aware applications through their use of distributed computing. In
- a distributed computing model the network application is partitioned
- into a workstation or front-end process and a server-based or back-end
- process. The workstation sends transactions to, and receives responses
- from, the server process. One example is a store-and-forward electronic
- mail system for a PC network. Users send electronic mail messages from
- their workstations using a message editor. The message is then sent and
- accepted by a mail server that runs in a network server on the LAN. The
- mail server stores and forwards the message along the delivery path until
- it is requested for delivery by the recipient.
-
-
- Few Distributed Apps
-
- The most frequently used applications on a network are standalone and
- network aware applications. There have been few network intrinsic or
- distributed network applications. Reasons for the lack of distributed
- network applications include the lack of a sophisticated operating
- system for the PC network platform, proprietary server environments, and
- the complexity of programming.
-
- During the 1980s, we've seen computing platforms downsized from
- mainframes and minicomputers to desktop personal computers that provide
- similar or even more raw computing power than their larger predecessors.
- Unlike the downsizing of computing platforms from the mainframe to the
- minicomputer, we have not seen a large-scale migration of crucial data
- processing applications to the desktop personal computer or PC network.
- The hardware platform has been in place with more powerful 80386-based
- workstations, but the software has, for the most part, not followed. This
- is because PC operating systems and PC LAN system software lacked the
- power and sophistication of a multitasking operating system required for
- many mission critical data processing tasks.
-
- Many believe that with IBM and Microsoft's introducing Operating System/2,
- applications once unable to operate on PC network systems will now
- migrate down to the PC network platform. IBM's Systems Application
- Architecture (SAA) is rooted in the concept of application and data
- portability across IBM's large, medium, and Personal System/2(TM) systems.
-
-
- Proprietary Environments
-
- The network system software that functions in a network file server is
- designed to provide high-performance, multiuser access to shared network
- resources such as disks and printers. Network system software vendors
- like 3Com, Novell, and IBM each implemented a proprietary means of
- making DOS a multitasking operating system, since network server software
- is by nature multitasking. The proprietary software environments, some
- of which were openly documented by the vendor, were all based on
- different application programming interfaces (APIs). An application
- developer who wanted to create a distributed network application for the
- leading network system software providers needed to implement the server
- portion of the application several times, once for each leading network
- system vendor. Nearly all application developers elected not to write to
- these proprietary interfaces for the reasons of maintenance,
- duplicate development effort, and inevitable support problems. Few
- distributed applications were developed to these proprietary interfaces,
- and most distributed applications were developed directly by the network
- system software providers who knew the proprietary environment and`were
- capable of supporting the software interfaces.
-
- The introduction of Operating System/2 changes the development
- environment dramatically. Now, leading network system software vendors
- will support OS/2 as an industry-standard multitasking kernel within the
- network server. Developers of network distributed applications have
- the advantage of an industry-standard operating system that has more
- than 250 application programming interfaces. Also, those who create
- applications that use OS/2 as the operating system for the workstation
- portion of the application have the added advantage of writing to the
- same APIs for both the workstation and the server software.
-
-
- APIs
-
- Historically, developers of network applications have had an opportunity
- to choose from several APIs for data-sharing and interprocess
- communication services. These include APIs supported by DOS 3.x and the
- Microsoft redirector, network BIOS or NetBIOS APIs, and APIs directly in
- the transport layer of network protocols.
-
- DOS 3.x and redirector APIs provide basic file-sharing and record-locking
- capabilities. Network aware applications such as dBASE III PLUS are
- written to these APIs. This class of application involves intensive data
- sharing.
-
- Transport-specific APIs enable a developer to create communications-
- oriented applications. Applications such as electronic mail, local and
- remote network bridging, and message routing have the advantage of using
- the specific capabilities of a given network transport. Unfortunately,
- applications developed to a specific interface are not portable if
- another network transport layer is used. For example, an application
- that interfaces directly to TCP/IP for its internetworking support cannot
- function on an IBM or ISO protocol without first being modified to
- interface with the specific functions of those respective network
- transport protocols.
-
- NetBIOS, a communication-intensive protocol introduced with IBM's PC Net
- in 1985, supports session-level interaction between workstations and
- servers. As the name implies, NetBIOS is a low-level protocol that
- requires the application developer to write directly to a network BIOS.
- Perhaps the greatest advantage of the NetBIOS interface is that it is
- supported or emulated by all leading network system software vendors. An
- application developer who writes to the NetBIOS API can be reasonably sure
- that the application will run on many network systems and therefore
- garner a large market share. However, NetBIOS requires the application
- developer to understand a detailed communication protocol at very low
- levels in the system, therefore making NetBIOS applications difficult to
- develop. Also, error conditions that result from NetBIOS transactions
- (send, receive, etc.) need special programming over and above that
- required by the underlying operating system. Applications developed to
- the NetBIOS protocol include electronic mail and transaction-oriented
- database applications.
-
-
- The OS/2 LAN Manager
-
- The Microsoft OS/2 LAN Manager is a network system software product that
- provides file, print, resource, security, and network management services
- to a work group using a local area network. A joint development project
- of Microsoft and 3Com, the OS/2 LAN Manager is based on an OS/2 network
- kernel, supports both DOS and OS/2 network workstations, has sophisticated
- network management features, and provides an open system with complete
- application programmer interfaces, including programming documentation and
- development tools.
-
- The Microsoft OS/2 LAN Manager includes both workstation and server
- software. The workstation software includes user interface and requester
- functions for DOS and OS/2 network workstations. The server operates on
- top of the OS/2 kernel. Thus, as an application, the OS/2 LAN Manager is
- hosted by OS/2 and is an OS/2 application in the network server. Because
- it functions as an OS/2 application in the server, it can take advantage
- of the features of OS/2, including the ability to use the 16Mb of
- protected-mode memory provided by OS/2. The protected-mode memory allows
- OS/2 LAN Manager to coexist with any other application written to OS/2
- APIs. Applications written to OS/2 can operate side-by-side in the same
- computer that is acting as a network server for a PC LAN. This is perhaps
- the most important benefit gained from the use of the OS/2 kernel.
-
- The OS/2 LAN Manager is based on OS/2, unlike network system software that
- is based on proprietary implementations of multitasking DOS. Any
- application developer will be able to create an application that uses
- any of the more than 250 APIs within OS/2 and the application can run
- side-by-side with the OS/2 LAN Manager. This frees developers from writing
- to proprietary server interfaces. It is important to note that IBM has
- announced a network system software product called OS/2 LAN Server, which
- also is hosted by OS/2.
-
-
- Protocol-Independent
-
- Applications written to take advantage of the security, network
- management, or any other LAN Manager feature are portable to many
- different network environments. This is because the OS/2 LAN Manager
- itself is designed to be a transport protocol──independent system. The
- diagram in Figure 1 describes the framework of multiple network transport
- layers supported beneath OS/2 LAN Manager. For reference, the Microsoft
- OS/2 LAN Manager architecture is mapped against the Open Systems
- Interconnection (OSI) model.
-
- Network system software like 3Com's 3+Open(TM) product line that incorporates
- the Microsoft OS/2 LAN Manager will be supported on multiple network
- transport layers. Network applications written to take advantage of
- OS/2 LAN Manager APIs will have an opportunity for a greater share of the
- market because the Microsoft OS/2 LAN Manager will support specific needs
- of more environments by giving the customer the choice of network
- transport. As shown in Figure 1, the OS/2 LAN Manager has several
- protocol-independent interfaces including the NetBIOS and hardware
- adapter driver (Media Access Control) levels. Future developments will
- yield a network transport programming interface and an IBM(R) LLC (Logical
- Link Control) 802.2 interface.
-
-
- Backward-Compatible
-
- The Microsoft OS/2 LAN Manager supports equivalent calls to today's
- application programmer interfaces (NetBIOS, DOS redirector calls) in both
- an OS/2 and DOS environment. Today's DOS and NetBIOS APIs are supported
- and are compatible with the installed base of network applications
- written to DOS and NetBIOS. These include both normal DOS calls and
- Interrupt 5CH calls from the OS/2 compatibility mode.
-
-
- An Open System
-
- The OS/2 LAN Manager is an open system. Application programmer interfaces
- are provided for all important system functions. The system is hosted by
- OS/2, making it fully compatible with the more than 250 APIs provided by
- the kernel, in addition to the APIs shown in Figure 2.
-
-
- IPC
-
- Highly integrated with the OS/2 kernel over which it operates, the
- Microsoft OS/2 LAN Manager extends the capabilities of OS/2 "pipes" or
- interprocess communication across the network, allowing an application
- to use remote procedure calls to processes running in the same computer
- or a different one elsewhere on the network. This extension or
- redirection of OS/2 interprocess communication across the network is
- often referred to as "named pipes" or redirected pipes. It is this ability
- to redirect interprocess communication across the network that is the
- foundation of distributed and network intrinsic applications (see
- Figure 3).
-
- Of course, named pipes are not required to build an application that
- uses PC LAN interprocess communication and remote procedure calls. What
- makes the OS/2 LAN Manager implementation of named pipes different is
- the ease of implementation for the developer when compared with similar
- implementations based on NetBIOS or other program-to-program communications.
- Where NetBIOS and other program-to-program protocols often require specific,
- in-depth knowledge of networking issues, named pipes provide a high-level
- interface that offers developers of DOS-based applications an opportunity to
- quickly and easily create network intrinsic applications.
-
-
- Remote Procedure Calls
-
- Before providing an in-depth comparison of the use of named pipes versus
- NetBIOS for remote procedure calls on a PC network, it helps to review the
- types of transactions required to perform this task. The example below is
- generalized to include the procedures necessary for a secure,
- transaction-oriented, comprehensive commercial application. Included in
- the application are user authentication, error logging, access control, and
- access logging.
-
- In general, the requesting workstation and responding server will have to
- perform the following tasks:
-
- ■ user asks for a session
-
- ■ user sends username
-
- ■ server sends a challenge that is used by the workstation to provide
- the correct password validation back to the server
-
- ■ user sends password
-
- ■ server validates password, username
-
- ■ user sends remote procedure call
-
- ■ server checks for access control (can the user perform the remote
- procedure call)
-
- ■ server performs proper error response and logging if
- access/permission denied
-
- ■ server performs remote procedure call
-
- ■ server logs access to server process used by remote procedure call
-
- ■ server closes session
-
- ■ server gets next request
-
-
- Named Pipes vs. NetBIOS
-
- Named pipes are transaction-oriented and provide a much higher-level
- interface to the application developer. The calling convention of named
- pipes is much less complicated than that required with a NetBIOS solution.
- A single named pipe function call equates to many NetBIOS calls. To
- demonstrate the relative simplicity of the named pipe API in relation to
- NetBIOS APIs, a side-by-side comparison is provided in Figure 4.
-
- It is important to note that the named pipe example is presented in C
- pseudocode and more closely approximates real C language code. The NetBIOS
- example is presented with a high-level procedural description of steps
- that would need significant embellishment to evolve to the detail of the C
- language pseudocode. This was done because the NetBIOS example would be
- too lengthy if provided in detailed C pseudocode. Even with a pseudocode
- example, the differences in complexity of the calling convention between
- named pipes and NetBIOS is obvious.
-
- A remote procedure call requires two basic pieces: the workstation portion
- that initiates the call and a server portion that responds to and acts
- on the call request.
-
- The function call necessary to request a remote procedure call from an
- OS/2 LAN Manager workstation to an OS/2 LAN Manager server is provided in
- Figure 4. For clarity, some of the arguments (like buffer lengths, for
- example) have been excluded from the C pseudocode example.
-
- You will notice that the DosCallNmPipe function takes the name of a
- server, the name of a pipe, application name and request, and receive
- buffer as arguments. The complete remote procedure call request is
- performed with a single function call. All user validation, auditing,
- error checking, and reporting is performed automatically by the OS/2
- LAN Manager server, since a named pipe can be shared by an OS/2 LAN
- Manager server. The OS/2 LAN Manager security system, network management,
- audit trail, resource accounting, and user validation are a side benefit
- of using named pipes.
-
- To provide a similar capability using a NetBIOS API on an OS/2 LAN
- Manager system would require workstation code with the functional
- characteristics shown in Figure 4.
-
- Notice that with the NetBIOS example, not only is the remote procedure
- call more complex, but special-purpose code must be developed to validate
- user access, report on resource usage, and handle special case error
- conditions. Named pipes are treated as any other shared server resource,
- and thus special-purpose code is not necessary as the error conditions,
- validation, and reporting are handled automatically by the Microsoft
- OS/2 LAN Manager. It is also important to note that the NetBIOS example
- does not include special code to handle error conditions that would
- require clean-up operations.
-
- Named pipes can be either stateless, like DosCallNmPipe, or allow remote
- procedure calls, like DosTransactNmPipe. Use of the DosCallNmPipe
- procedure call provides for stateless remote procedure calls (sessions
- only maintained during a transaction) and thus are less prone to error and
- don't require special clean-up. Use of DosTransactNmPipe allows a series
- of RPCs on the same session with the network server.
-
- In addition to error conditions that result from session management, hard
- errors, such as Abort, Retry, Ignore, Fail, occur. Since named pipes are
- an extension of OS/2 pipes, built-in OS/2 error recovery is
- automatically used should a hard error occur. With NetBIOS, hard errors
- must be anticipated and dealt with within the application code itself.
-
-
- Remote Procedure: Server Portion
-
- Remote procedure calls that are initiated using named pipes from the
- workstation must be supported by remote procedure management code on the
- network server. The pseudocode in Figure 5 shows the APIs required by
- the network server portion of the distributed application, as well as
- the pseudocode necessary to provide similar capabilities if NetBIOS is
- used rather than named pipes.
-
- Note that in the NetBIOS example, all password and user verification,
- reporting, auditing, and resource management must be supported directly by
- the application code running in the network server.
-
- The system-level capabilities - portability, stateless operation,
- multiple RPC calls, auditing, security, administration, error handling,
- and higher performance - are built into the Microsoft OS/2 LAN Manager and
- are therefore automatically available to any application that uses named
- pipes for interprocess communication across the network.
-
-
- Portability
-
- OS/2 LAN Manager is hosted by the OS/2 kernel; therefore, applications
- written to OS/2 APIs can be ported to perform remote procedure calls and
- function in a distributed configuration on the PC LAN with only minor
- modifications to the application code. This is not true if the application
- was ported from OS/2 to use NetBIOS as an interprocess communication API.
- It is also important to note that applications written to OS/2 can run
- without modification in an OS/2 LAN Manager network server. Of course,
- this is not the case with all network system software. Many vendors who
- provide PC network systems for business work groups don't provide an
- effective OS/2 server environment for OS/2 applications installed in the
- server.
-
-
- Stateless Operation
-
- A stateless distributed application maintains a session with the server
- only during the length of a single procedure call. Each procedure call to
- the server from the workstation creates a session, performs the remote
- procedure call, and closes the session. With named pipes this is
- performed using DosCallNmPipe. To perform the remote procedure call
- using NetBIOS, the NetBIOS application must perform a call, send,
- receive, and hang up. The NetBIOS example requires the session to live
- longer than a simple request/receive transaction. This is particularly
- important with error management. The longer a session is open, the
- more likely an error may occur. If you don't have a stateless design,
- special case code has to be provided to perform a clean-up from
- potential errors.
-
-
- Multiple RPC Calls
-
- Named pipes also include procedure calls that are useful when the
- application transacts multiple remote procedure calls on the same session
- to the server. For some applications, this is the desired means for remote
- procedure calling. Applications that perform normal DOS reads, DOS
- writes, or DosTransactNmPipe calls to a named pipe are operating in such a
- multicall mode, which may offer performance advantages because the
- session is maintained across multiple transactions and doesn't have to be
- reestablished for every transaction between the requester and the
- server.
-
-
- Auditing
-
- Many distributed applications require sophisticated audit trail facilities
- that track who performed the remote procedure call, from which
- workstation, at what time, and how long the remote procedure call took.
- Using a NetBIOS-based distributed application, all of the functions
- required for auditing would have to be specially developed as part of the
- distributed application. With the Microsoft OS/2 LAN Manager, the
- network administrator can flag one or more specific named pipes that exist
- on the server and mark them to be audited; this causes a record to be
- entered in the OS/2 LAN Manager audit trail each time a named pipe is
- opened or closed. The record generated in the audit trail can be used as
- the foundation of a bill-back system. It includes the date and time of
- access, who accessed the named pipe, the type of access (open/close), and
- the duration of the access.
-
-
- Security
-
- The Microsoft OS/2 LAN Manager incorporates a sophisticated user
- security system that includes encrypted user log-on validation, access
- control list, error reporting, and real-time event notification of
- attempted security breaches should access to a shared resource be
- attempted by an unauthorized user. All of these capabilities are
- available to a distributed application that uses named pipes as the
- means for interprocess communication across the network. Access to named
- pipes is controlled by the server's standard user logon, password
- encryption, group membership, and access permission mechanism. The
- capabilities are provided without requiring any additional programming by
- the application developer.
-
- Treated by the server as a shared resource, the named pipe can be placed
- in the server's resource list and given access permission using the OS/2
- LAN Manager administrative software. Once logged as a shared server
- resource, the OS/2 LAN Manager will report on the status of the resource
- just as it would any other shared resource on the network. Attempts to use
- the named pipe resource will be logged in the system audit trail. Should
- excessive unauthorized access to the resource be attempted, a real-time
- message will be sent to advise a predetermined network administrator of
- the attempted security breach. Finally, network errors resulting from the
- use of the named pipe will be automatically logged in the system's error
- file and can be reviewed by the network administrator.
-
-
- Administration
-
- The OS/2 LAN Manager has a window-oriented administrative user
- interface that supports real-time administration of shared network
- resources. If distributed applications are designed to use named pipes as
- the means for interprocess communication, the LAN Manager administrative
- interface can be used to view and manage access to the redirect pipes
- without any development specifically embedded in the distributed
- application. OS/2 LAN Manager provides management of named pipes,
- including the ability to view real-time user access to the named pipe.
- Data includes local machine name, user name, resource name, total session
- time, and session idle time.
-
- In addition, OS/2 LAN Manager provides the ability to force a user to
- disconnect from a named pipe and automatically disconnect workstation and
- server sessions based on user activity and an administrator defined
- timeout parameters. If a named pipe is open between a workstation and
- server (there is an open handle), the session will not timeout. A built-in
- pop-up message system can be used directly by the distributed application to
- inform users with a real-time message. The message system can also be used
- by the network administrator to send messages to individual users.
-
-
- Error Handling
-
- Pipe management code is part of the operating system kernel within OS/2.
- It adheres fully to the operating system's method of error reporting,
- including use of standard OS/2 error codes and issuance of hard error pop-
- ups where appropriate with abort/retry/ignore/fail error handling. Because
- NetBIOS session capability is not a supported interprocess communication
- within the operating system, distributed applications written to NetBIOS
- must include a significant amount of specific error-handling code.
-
- Named pipes also provide advantages for the server portion of the remote
- procedure code. If a named pipe fails during processing, the operating
- system performs all the cleanup. But if any NetBIOS send fails, the
- application will have to cancel any asynchronous receive requests that
- have been posted and hang up the session. Each of these clean-up tasks
- could generate an error as well, from which the application must be
- programmed to recover.
-
-
- Performance
-
- The Microsoft OS/2 LAN Manager named pipe API supports function calls
- such as DosTransactNmPipe and DosCallNmPipe that do entire transactions in
- a single system call. By contrast, multiple NetBIOS calls would be
- necessary to perform the same function as the single named pipe call.
- Because named pipes call the operating system less often, distributed
- applications based on named pipes for remote procedure calls and
- interprocess communication are likely to yield high-performance results.
-
-
- Tradeoffs
-
- Many of the advantages achieved by the developer who uses named pipes for
- PC network interprocess communication have been outlined. There are,
- however, costs to the developer who uses OS/2 LAN Manager named pipes,
- which deserve serious consideration from any developer who is planning
- to create a distributed network application.
-
- Currently, named pipes have been announced as a fully supported API on
- systems that incorporate the Microsoft OS/2 LAN Manager. 3Com's 3+Open
- product line is an example of one such system. Although it is reasonable
- to assume that other vendors who provide network system software product
- lines will incorporate the Microsoft OS/2 LAN Manager product, and that
- these systems will also support named pipes, there are many more installed
- network systems that support NetBIOS and will continue to do so. The
- developer of a network application will, at least initially, choose
- between capability and installed base market share. For many developers,
- the advantages of named pipes and the market position of companies who
- have announced Microsoft OS/2 LAN Manager-based systems is
- encouragement enough to forgo NetBIOS and develop to the named pipe
- interface. Even if a developer of network applications already knows and
- understands the NetBIOS API, using named pipes will require additional
- education.
-
- Microsoft has taken direct steps toward reducing the need for further
- education with its Microsoft University classes. These classes deal with
- the fundamentals of developing OS/2 applications as well as applications
- development for the Microsoft OS/2 LAN Manager. Microsoft is also
- sponsoring several OS/2 LAN Manager developers' conferences this year.
- The high-level interface and simplicity of named pipes, the classes at
- Microsoft University, and the developers' conference will benefit
- developers of any network application.
-
-
- Application Platform
-
- Although the success of network systems incorporating the OS/2 LAN Manager
- won't be known until they are introduced about mid-year 1988, the Microsoft
- OS/2 LAN Manager provides a rich platform for application development.
-
- The strong points of the Microsoft OS/2 LAN Manager currently available to
- developers of network applications and end customers include:
-
- ■ use of an industry-standard multitasking operating system, OS/2
-
- ■ high performance and reliability
-
- ■ open system architecture
-
- ■ support for OS/2 APIs as well as NetBIOS and DOS redirector calls
-
- ■ support for OS/2 pipes and extension of them across the network using
- named OS/2 pipes
-
- ■ rich system-level function that lets applications benefit from the
- administrative functions already built into the system
-
-
- Trends
-
- The advent of IBM OS/2 and the use of industry standard multitasking
- operating systems as the kernel to network system software provides an
- opportunity for the development of applications that reside in the network
- server and provide distributed computing power to the PC network. Network
- applications that once had to be developed to use proprietary, vendor-
- specific operating system interfaces will now be developed to industry-
- standard OS/2 operating system interfaces. In addition, Microsoft has
- simplified network interprocess communication with the OS/2 named pipes
- capability. OS/2 LAN Manager APIs can significantly reduce the overhead of
- developing a network intrinsic application. People in the industry believe
- that the OS/2 network servers and these new APIs will result in many new
- applications, such as database servers, mail servers, name/directory
- servers, document library services, accounting (using a distributed database
- server as the back end), and communication gateways.
-
- The industry direction by Microsoft, IBM, and 3Com of providing and
- marketing open system server platforms based on Operating System/2 as an
- industry-standard operating system kernel will benefit both the developer
- and end customer of PC network systems with more powerful and functional
- application software.
-
-
- Figure 1: The Microsoft OS/2 LAN Manager has a protocol-independent design.
-
- ┌─────────────────────────────────────────────────────────────────────────┐
- │ ╔═════════════╗ ╔═══════════════════════════════════╗ │█
- │ ║ Application ║ ║ Microsoft OS/2 LAN Manager Server ║ │█
- │ ╟─────────────╢ ║ and Workstation Software ║ │█
- │ ║ Presentation║ ╚═══════════════════════════════════╝ │█
- │ ╚═════════════╝ │█
- │ │█
- │ ╔═════════════╗ ╔════════╗ ╔════════╗ ╔════════╗-NetBIOS │█
- │ ║ Session ║ ║ IBM ║ ║ ║ ║ ║ │█
- │ ╟─────────────╢ ║NetBIOS/║ ║ TCP/IP ║ ║ ISO ║ │█
- │ ║ Transport ║ ║ Token ║ ║ ║ ║ ║ │█
- │ ╟─────────────╢ ║ Ring ║ ║ ║ ║ ║ │█
- │ ║ Network ║ ╚════════╝ ╚════════╝ ╚════════╝ │█
- │ ╚═════════════╝ │█
- │ │█
- │ ╔═════════════╗ ╔═══════════════════════════════════╗-802.2 LLC │█
- │ ║ Data Link ║ ║ Adapter Interface and ║ │█
- │ ╟─────────────╢ ║ Protocol Manager ║ │█
- │ ║ Physical ║ ╚═══════════════════════════════════╝-Media │█
- │ ╚═════════════╝ Access │█
- │ Control │█
- └─────────────────────────────────────────────────────────────────────────┘█
- ██████████████████████████████████████████████████████████████████████████
-
-
- Figure 2: System functions supported by an application programming
- interface include, but are not limited to, those shown here.
-
- Network Interface
- Media Access Control (network adapter
- driver interface)
- Protocol manager (to support multiple,
- simultaneous network protocols)
-
- Network Management/Administration
- Audit trail
- Messaging
- Alerter
- Error log
- Network error reporting
- Network statistics
-
- Input/Output Devices and Pipes
- Named pipes (redirected pipes)
- Spoolers
- Character devices
- NetBIOS
- Mail slots
-
- Network Access
- Use/un-use network resources
- Access control list security
- Server/workstation configuration
- Resource sharing
- Network console
- Net Run (OS/2 compute server)
- Logon scripts
- User profiles
-
-
- Figure 3: OS/2 pipes are used to redirect interprocess communication within
- a single computer. Named pipes redirect interprocess communication
- and make it possible for applications to transparently use
- computing resources in other workstations or servers on the
- network.
-
- User
- ╔══════════════╗ ┌──────Interface──────┐
- ║ ░░░░░░░░░░░ ║█ │ │
- ║ ░░░░░░░░░░░░ ║█ OS/2 Pipe OS/2 Pipe
- ║ ░░░░░░░░░░░░ ║█ │ │
- ║ ░░░░░░░░░░ ║█
- ╔═╝──────────────╚═╗ Database ──OS/2 Pipe── Report
- ║ ---- ║█ Manager Writer
- ╚══════════════════╝█ ─────────────────────────────────────────────────────
- ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ╔══════╗
- Standalone PC ║│ ║█ Server
- ║│▄▄▄▄▄║█ ╔═══════════╗
- ║┌───┬┐║█ ║ ░░░░░░░░ ║█
- ║│ ││║█ ║ ░░░░░░░░░ ║█
- ┌──────────Name Database ║│ ││║█ ║ ░░░░░░░░░ ║█
- User (Redirected)────Manager ║│ ││║█PC Network║ ░░░░░░░ ║█
- Interface OS/2 Pipe ║└───┴┘║█ ╔═╝───────────╚═╗
- │ OS/2 Pipe ║||||||║█ ║ ---- ║█
- └──────────Name ┌─ ║||||||║█ ╚═══════════════╝█
- (Redirected)──┘ Report ╚══════╝█ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
- OS/2 Pipe Writer ▀▀▀▀▀▀▀ Workstation
-
-
- Figure 4: A pseudocode comparison of a LAN manager API call (shown on top)
- and the equivalent NetBIOS calls (shown below).
-
- LAN Manager API Call
-
- DosCallNmPipe("\\servername\pipe\appname", RpcRequBuf, RpcRespBuf)
-
- NetBIOS API Call
-
- Begin Procedure
- Get username from the user
- Get password from the user
- Establish session with server
- Send username to server
- Receive server challenge (used for password encryption)
- Do the encryption challenge using server challenge and user password
- Result of logic is challenge response
- Send challenge response with remote procedure request to server
- Receive remote procedure call response
- Close session with server
- End Procedure
-
-
- Figure 5: The LAN Manager itself handles a great deal of the administrative
- details required through NetBIOS.
-
- LAN Manager API Call
-
- DosMakeNmPipe(\"pipe\appname", handle)
- loop /* for each remote procedure call */
- DosConnectNmPipe(handle)
- DosRead(handle, RpcReqBuf)
- Do the remote procedure call
- DosWrite(handle, RpcRespBuf)
- DosDisconnectNmPipe(handle)
- endloop
-
- NetBIOS API Call
-
- Begin_Loop
- Listen for user session
- Establish user session
- Receive username
- Do encryption logic to generate challenge
- Send challenge
- Receive challenge response and remote procedure call request
- Do encryption logic to validate user challenge response
- Open and match username in application specific access control file
- If access control supports group memberships, then verify membership
- If validation fails,
- Generate an audit trail record that access was denied
- Send an access denied response
- Close session
- GotoBegin_Loop
- Do the remote procedure call
- Send the remote procedure call response
- Close the session
- Calculate time duration of remote procedure call handling and
- generate audit trail record of service provided for this user
- End_Loop
-
- ████████████████████████████████████████████████████████████████████████████
-
- Writing OS/2 Bimodal Device Drivers: An Examination of the DevHIp API
-
- Ray Duncan
-
- Device drivers are the modules of an operating system that issue commands
- directly to peripheral devices and cause data to be transferred between
- those devices and RAM. Drivers are thus the bottom, most hardware-
- dependent layer of the system software. They shield the operating system
- kernel from dealing with hardware I/O port addresses and the peculiarities
- and characteristics, such as number of tracks or sectors per track, of a
- particular peripheral device, just as the kernel in turn shields
- applications programs from the details of file and memory management.
-
- By convention, Microsoft(R) OS/2 device drivers reside in individual disk
- files with the extension .SYS. The essential OS/2 drivers for the keyboard,
- display, and disk device are selected on the basis of the hardware
- environment and automatically linked to the operating system during booting.
- Other optional device drivers can be loaded by DEVICE= statements in the
- CONFIG.SYS file on the boot disk and are referred to as installable drivers.
- However, all OS/2 systems drivers have the same physical and logical
- structure and an identical relationship to the OS/2 kernel.
-
- OS/2 device drivers are in many ways similar to MS-DOS(R) device drivers,
- but they also have unique capabilities and requirements, including:
-
- ■ fully interrupt-driven operation
-
- ■ bimodal (real mode and protected mode) operation
-
- ■ support for multitasking and overlapped I/O
-
- ■ the ability to deinstall after the driver is fully initialized
-
- ■ support for device monitors (applications that can register with
- the driver to filter its data stream)
-
- ■ support for ROM BIOS service calls by real-mode applications
-
- The experienced MS-DOS programmer must be particularly wary of
- terminology that sounds familiar even though the associated driver function
- is very different. A full-fledged OS/2 driver for any given device is at
- least an order of magnitude more complex than its MS-DOS equivalent.
-
-
- Device Driver Types
-
- OS/2 device drivers are categorized into two groups: character device
- drivers and block device drivers. Whether a given driver is a character
- device or block device driver determines how the associated device is
- viewed by OS/2 and what functions the driver itself must support.
-
- Character device drivers control peripheral devices that perform input
- and output a character (or byte) at a time, such as a terminal or a
- printer. A single character device driver ordinarily supports a single
- hardware unit, although a given file can contain more than one driver.
- Each character device has a one- to eight-character logical name, and can
- be opened by using this name for input or output by an application
- program as though it were a file. The logical name is strictly a means of
- identification of 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 a byte at a time. Block devices are usually randomly
- addressable, such as floppy disk or hard disk drives, but they can also be
- sequential in nature, such as a magnetic tape drive. 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 hard disk.
-
- OS/2 assigns single-letter drive identifiers (A:, B:, and so on), instead
- of logical names, to block devices. The first letter assigned to a given
- block device driver is determined solely by that driver's position in the
- chain of all drivers, that is, by the number of units supported by the
- block drivers that have been loaded before it. The total number of
- letters assigned to the driver is determined by the number of logical
- units that the driver supports.
-
-
- Device Driver Structure
-
- OS/2 device drivers are written as small-model Macro Assembler programs
- with one code and one data segment and are linked as a segmented or "new"
- EXE file. Figure 1 is a diagram of the physical structure of a device
- driver file; the data segment always lies in the first part of the file,
- with a structure called the device driver header at its beginning. The
- header contains information about the driver that the OS/2 kernel can
- inspect when it needs to satisfy an I/O request by an applications program
- (see Figures 2 and 3); all of the device drivers in the system are
- chained together through pointers that are stored in the device driver
- headers.
-
- A driver has two major components: the strategy routine and the interrupt
- routine. Although they have the same names as the two most important
- sections of an MS-DOS installable driver, they function in a very different
- manner.
-
-
- The Strategy Routine
-
- The OS/2 kernel will initiate an I/O operation by calling the driver's
- strategy routine (whose offset is found in the device driver header),
- passing the address of an information packet called a request header in
- registers ES:BX. The first 13 bytes of the request header have a fixed
- format and are followed by data that varies according to the operation
- being requested (see Figure 4).
-
- The most crucial field of the request header is the command code, which
- selects a particular driver subfunction (see Figure 5). Many of the
- subfunctions can be carried out immediately at the time of the strategy
- call; in such cases, the driver simply places any necessary information to
- be returned to the kernel into the request header and sets the done bit in
- the status word.
-
- However, some subfunctions, such as read and write, involve an
- unpredictable delay and cannot be completely carried out during the
- strategy call without an adverse impact on overall system performance.
- The request headers for such subfunctions are chained onto the driver
- queue or work list, the actual I/O operation is started if the physical
- device happens to be idle, and then the strategy routine returns control
- to the kernel so that other work within the system can go forward.
- Incidentally, block device drivers are expected to sort their pending
- work list so as to minimize the average transfer time.
-
- The driver strategy routine normally executes at the highest privilege
- level (Ring 0), with the exception of the initialization subfunction,
- which executes at Ring 2 (the same as an application with I/O privilege).
- The strategy routine must be fully reentrant and capable of processing
- multiple requests simultaneously.
-
-
- The Interrupt Routine
-
- The driver interrupt routine is entered from the kernel as a result of a
- hardware interrupt from the physical device, when an I/O operation is
- either finished or has been aborted due to a hardware error. The
- interrupt routine queries the peripheral controller to determine the
- outcome of the I/O operation, sets the done bit and the appropriate
- status and error information in the request header, removes the request
- header from the work list, and notifies the kernel that the operation is
- complete.
-
- The interrupt routine then examines the driver's work list to determine
- whether other I/O operations are pending for the device. If additional
- request headers are waiting, the interrupt routine initiates the
- operation that is at the head of the queue before returning control to
- the kernel by clearing the carry flag and issuing a FAR RET. In cases
- where several devices (and their drivers) share the same interrupt level,
- the kernel will call 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.
-
- Since hardware interrupts are by nature asynchronous and unpredictable, an
- OS/2 device driver must be capable of properly servicing an interrupt in
- either protected mode or real mode. In order to assist the driver, OS/2
- always provides special bimodal pointers to request headers-pointers that
- are valid in either real mode or protected mode. This is accomplished by
- reserving an area of physical memory to hold request headers and a set of
- selector values such that the selector for each header is exactly the
- same as its segment (paragraph) address. As an extra aid to the driver,
- OS/2 locks down memory segments that are the source or destination of a
- data transfer and converts their segment:offset or selector:offset
- addresses into linear 32-bit physical addresses before passing the read or
- write request to the driver.
-
-
- Unique Aspects
-
- Three particularly interesting features of OS/2 device drivers have no
- counterpart under MS-DOS: the necessity to provide ROM BIOS-equivalent
- services for real-mode applications, support for device monitors, and
- the ability of a driver to deinstall itself on demand.
-
- The ROM BIOS requirement stems from the propensity of many popular MS-DOS
- application programs to call the ROM BIOS directly rather than perform
- their I/O in a well-behaved manner through documented MS-DOS services.
- The most frequent reason for such ROM BIOS calls is to improve performance
- or because MS-DOS does not offer the necessary functionality (EGA palette
- programming, for example). Since the ROM BIOS code is mostly incompatible
- with the constraints of a protected-mode multitasking environment──it is
- not reentrant and it frequently masks off interrupts, among other
- problems──an OS/2 driver must capture the interrupt vectors that 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 keyboard enhancers,
- macro-expanders, print spoolers, and similar utilities to be written and
- installed in a hardware-independent manner. An applications program
- that wishes to filter the data stream of a particular character device
- driver can issue an OS/2 Application Program Interface (API) function call
- to register as a monitor for that device. Then the OS/2 kernel acts as
- the intermediary and asks the driver if it wishes to accept the monitor
- registration. If the driver assents, it will cooperate with the kernel to
- pass each character 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.
-
- Finally, extension of the device driver definition with the DeInstall
- command code provides efficient use of memory and a more controlled
- environment for character drivers. A character device driver, under MS-
- DOS, can be superseded by simply 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 first sends a request header with the DeInstall command
- code to the previous driver. The first driver can then release its
- interrupt vectors and memory and return a success status, after which
- the new driver is allowed to initialize itself. Alternatively, if the
- first driver refuses the DeInstall command by returning an error code,
- the kernel aborts the installation of the new driver with the same name.
-
-
- Device Driver Helpers
-
- Under MS-DOS, device drivers must be self-contained. They can use a very
- limited subset of the Interrupt 21H function calls during their
- initialization (character output, get MS-DOS version, get or set
- interrupt vector), but once the system is fully loaded and running, device
- drivers are not allowed to invoke any MS-DOS system services at all. MS-
- DOS, as a single-tasking operating system, assumes that only one I/O
- request will be in progress at any given time. If a driver that is in the
- process of carrying out an I/O request from the kernel in turn requests a
- different operation from the ker-nel, the context of the original
- request is destroyed, and the system will crash.
-
- OS/2, on the other hand, was designed specifically for multitasking, and
- most of its internal routines are fully reentrant. As a result, OS/2
- provides a battery of services to device drivers, called device driver
- helpers, or DevHlps, much as it does to application programs, although the
- nature of these services is of course much different. Moving
- functionality that all drivers need into the kernel makes drivers
- smaller, simpler, and easier to debug and ensures that critical driver
- activities are carried out uniformly and efficiently.
-
- Unlike the kernel's API, in which parameters are passed on the stack and
- 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 the DevHlp entry point is called, a particular DevHlp function is
- selected by the value in register DL. Additional parameters and addresses
- are passed in other general registers as demanded by the particular function
- being requested. The status of a DevHlp function is usually returned to the
- driver in the zero or carry flags, error codes in register AX, and addresses
- in registers DS:SI or ES:DI (see Figure 6 for an example of a
- typical DevHlp call).
-
- ───────────────────────────────────────────────────────────────────────────
- Also see:
- Summary of the way Device Drivers Queue and Process I/O Requests
- ───────────────────────────────────────────────────────────────────────────
-
- DevHlps and Driver Modes
-
- Whenever an OS/2 device driver has control of the CPU, it executes in one
- of four different modes or contexts: kernel mode, interrupt mode, user
- mode, and init mode. Understanding these modes and their implications is
- essential to the task of writing an OS/2 driver, since the DevHlp
- services available to a driver are determined by its current mode.
-
- A driver is in kernel mode when its strategy routine is called by the
- kernel, usually as the immediate result of an I/O request by an application.
- The driver executes at the highest privilege level (Ring 0). Nearly every
- DevHlp service is available to the driver in kernel mode.
-
- The driver executes in interrupt mode as the result of a hardware
- interrupt. The OS/2 kernel fields the interrupt, saves all registers, and
- enters the driver's interrupt routine via a far call. The driver executes
- at the highest privilege level (Ring 0). A somewhat restricted set of
- DevHlp services is available to a driver in interrupt mode.
-
- The driver executes in user mode when it is entered as a result of a
- software interrupt by an application executing in the real-mode session
- (sometimes called the DOS 3.x compatibility box). The only drivers
- that must concern themselves with this mode are those that provide
- equivalents to the original IBM(R) PC ROM BIOS services in a manner
- consistent with the multitasking and memory protection requirements of
- OS/2, such as the keyboard, video, or floppy disk driver. Since a driver
- only runs in user mode when the CPU is in real mode, the concept of
- privilege levels is irrelevant, but you can think of the driver as
- executing at the highest privilege level since no protection mechanisms
- are operative. Relatively few of the DevHlp services are available to
- the driver in user mode.
-
- A driver runs in init mode only once: when its strategy routine is called
- with a request packet containing the initialization command code,
- immediately after the driver is loaded into memory during the system boot
- process. In init mode, the driver runs as though it were an application
- program IOPL segment, that is, at the next-to-lowest privilege level
- (Ring 2). A limited set of dynamic-link API calls, mostly concerned with
- file management (DosOpen, DosRead, DosWrite, and so on), as well as a
- subset of the DevHlp services (mainly those associated with hardware and
- memory management), are available to a driver in init mode.
-
- Task time and interrupt time are two other terms in the OS/2 device driver
- documentation regarding driver execution context. A driver is executing
- at task time when it is called synchronously as the direct result of
- some application program API request or real-mode software interrupt;
- this includes both the user mode and kernel mode contexts described
- above. Interrupt time refers to driver routines being invoked
- asynchronously as the result of an external hardware event; consequently,
- the identity of the currently executing process and the state of the CPU
- (real mode or protected mode) is unpredictable.
-
-
- Categories of DevHlps
-
- The roughly 40 DevHlp functions fall into nine categories (see
- Figure 7): process management, memory management, hardware management,
- request packet management, character buffer management, timer management,
- monitor management, semaphore management, and ABIOS communication
- (IBM PS/2(TM) only).
-
- Some of these categories of services 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 they provide access to memory
- addresses that the driver would otherwise be unable to reach. The others
- are present only for convenience or to assist in the construction of
- unusual or highly complex drivers. A more detailed look at the most
- commonly used groups of DevHlp functions also brings up some interesting
- points about the operation of OS/2 device drivers and their relationship
- to the kernel.
-
-
- Process Management
-
- The basic technique by which a driver performs overlapped or asynchronous
- I/O is very simple. 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 header status word. The kernel
- then suspends the thread that initiated the I/O request. (For more
- information about OS/2 threads and processes, see "OS/2 Multitasking:
- Exploiting the Protected Mode of the 80286," MSJ, Vol. 2 No. 2, p. 27.)
-
- When the I/O operation is eventually completed, the driver's interrupt
- routine updates the request packet associated with the operation with the
- appropriate status word and any other necessary information 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.
-
- A driver can also use the DevHlps Block and Run to suspend a thread while
- an I/O operation is in progress, particularly if there is a lengthy
- amount of code in the driver that must be executed after the operation is
- complete. Putting such code in the strategy routine is preferable to
- putting it in the interrupt routine, since 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 will cause the kernel to wake up the original
- thread within the strategy routine, which can proceed with any necessary
- post-I/O processing and then exit back to the kernel with the request
- header's done bit set. The biggest problem with this approach is ensuring
- that the 32-bit value used in a pair of Block and Run calls is unique
- within the system, since Run wakes up all threads that have blocked with
- that particular 32-bit identifier.
-
- The DevHlp services Yield and TCYield are provided for drivers that need
- to transfer long strings of data from one memory location to another, such
- as a RAMdisk, or whose devices do not support interrupts. A call to Yield
- simply tells the kernel's scheduler to give any threads with the same or
- higher priority a chance to run before returning control to the
- currently executing thread, while TCYield allows only those threads
- that have a time-critical priority to take a turn. A driver can check the
- value of YieldFlag or TCYieldFlag (two variables whose addresses are
- returned by GetDOSVar, another DevHlp) to determine whether any other
- threads are waiting for the CPU and bypass the call to Yield or TCYield if
- no other thread is eligible to execute. Such a check along with a possible
- Yield should be made at least every 3 milliseconds to avoid interference
- with the proper operation of other drivers.
-
-
- Hardware Management
-
- Since the strategy and interrupt routines of device drivers execute at
- the highest privilege level (Ring 0), they have a free hand with the
- machine if they want it. As a result, two different drivers that access
- the same hardware addresses, such as a serial mouse driver and a
- communications driver that are both configured for COM2, can theoretically
- come into conflict. However, OS/2 has DevHlps to arbitrate interrupt
- vector and 8259 Programmable Interrupt Controller (PIC) usage for
- cooperating drivers. Well-behaved drivers will use these DevHlps to gain
- access to the hardware resources they need.
-
- The DevHlp services SetIRQ and UnsetIRQ assist drivers in capturing or
- releasing interrupt vectors. When a driver calls SetIRQ to hook its
- interrupt handler to a given interrupt request (IRQ) level, 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.
-
- When a hardware interrupt occurs, a dispatcher in the OS/2 kernel
- initially receives control, saves all registers, then enters the driver's
- interrupt handler via a far call. When several drivers share an
- interrupt level, the dispatcher calls each driver's handler in turn.
- The handler must access its device and determine whether that device
- caused the interrupt. If the driver finds that its own device did not
- trigger the interrupt, it returns to the kernel with the carry flag set.
- On the other hand, if the handler finds that it owns the interrupt, it
- must service its device, possibly start another I/O operation if any are
- waiting, call the DevHlp service EOI to dismiss the interrupt at the 8259
- PIC, and then return to the kernel with the carry flag clear. Note that,
- although drivers can directly access their own device's ports,
- manipulation of the PIC should be reserved for the kernel to isolate
- drivers 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 load time. Later
- on, if the driver needs more memory for tables, buffers, or other data
- structures, it can use the DevHlp services AllocPhys and FreePhys to
- dynamically allocate and release such memory. The memory that is assigned to
- the driver by AllocPhys is not movable or swappable, and the driver can
- specify whether it wants the memory located above or below the 1Mb boundary.
-
- One of the trickiest aspects of an OS/2 driver's operation is memory
- addressing. Since a driver must be able to execute and handle interrupts
- equivalently in either protected mode or real mode, it might encounter
- three types of addresses: protected-mode selector:offset pairs, real-
- mode segment:offset pairs, and the linear or physical 32-bit addresses
- used by DMA channels and some devices. The operating system's virtual
- memory manager, which may shuffle segments around to collect unused
- fragments together or swap segments out to disk, must also be taken into
- account.
-
- 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 with a request
- packet containing the read or write command codes, it passes the driver a
- physical 32-bit address that has already been "locked," that is, the
- virtual memory manager has been notified that the memory in question
- should not be moved or swapped. When the kernel passes the driver an
- address of a structure (such as a request packet) or a procedure (such as
- the DevHlp entry point) that the driver will need to access directly, it
- arranges things so that the address is valid in either real mode or
- protected mode, that is, the protected-mode selector for the structure or
- procedure is the same as its real-mode segment. Such an address is called
- a bimodal pointer.
-
- Next, the kernel provides six DevHelp functions which assist a driver by
- converting physical 32-bit addresses to virtual (segment:offset or
- selector:offset) addresses and back again: AllocateGDTSelector,
- PhysToGDTSelector, PhysToUVirt, UnPhysToVirt, and VirtToPhys. The first
- three are used to access static structures (such as memory allocated by
- AllocPhys or an adapter's memory-mapped I/O locations). The remainder are
- used when a driver needs temporary access to data that is transient or
- belongs to a process. The PhysToVirt service is particularly interesting,
- because it permits a driver which is executing in real mode to address
- memory locations above the 1Mb boundary.
-
- If the driver is running on a PC/AT(R) or compatible and has to reach
- extended memory in real mode, PhysToVirt uses the undocumented LOADALL
- instruction to set the CPU's "shadow" addressing registers, which are
- normally loaded invisibly from a descriptor table when a segment register
- is loaded, so as to allow the memory access. On a PS/2, PhysToVirt takes
- advantage of hardware support for faster mode switching and simply
- switches the CPU into protected mode, providing the driver with a selector
- to the memory. When the driver is finished with the virtual address it
- obtained from PhysToVirt, it calls the DevHlp UnPhysToVirt so that the
- selector can be reused, at which time the kernel will also restore the
- original CPU mode if necessary.
-
- Finally, 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 provides the DevHlp services Lock, Unlock, and VerifyAccess. The
- driver first calls VerifyAccess with the selector, offset, and length of
- the memory involved in the requested operation to make sure that the
- application is really entitled to access the memory. If VerifyAccess
- succeeds, the driver calls the Lock function to immobilize the segment
- and VirtToPhys to obtain the physical address, and then proceeds with the
- I/O operation, calling Unlock afterwards to put the segment back under
- control of the system's memory manager.
-
-
- Monitor Management
-
- Under MS-DOS, terminate-and-stay-resident (TSR) utilities have been
- tremendous commercial successes. Such utilities intercept keystrokes at
- the hardware or BIOS level while other applications are running and take
- some special action when a particular key, called a hot key, is detected.
- In order to make such utilities possible in protected mode, OS/2 allows
- applications to register as a monitor for a specific character device,
- if supported by the device driver. The raw data stream from the device is
- then passed through a pair of buffers belonging to the application before
- it is transferred from the driver to the kernel; the application can
- delete, add, or remap characters in the data stream as it chooses or
- simply watch for its hot key.
-
- As you might expect, a driver's support for monitors involves a rather
- complicated interplay of calls from the application to the kernel, the
- kernel to the driver, and the driver to the kernel, and requires a high
- degree of cooperation among the three to preserve the integrity of the
- system. A special kernel module, called the monitor dispatcher,
- arbitrates among the three components and takes care of moving data from
- the driver through the buffers of one or more monitors and then back to
- the driver.
-
- An application registers as a monitor for a character device by calling
- DosMonOpen with the logical name of the device. It then calls DosMonReg
- with the monitor handle returned by DosMonOpen and the addresses of two
- buffers that will be used for input and output of data packets from the
- driver. If registration succeeds, the application becomes an integral
- part of the data stream and must use the functions DosMonRead and
- DosMonWrite to pass the character data through its buffers in a timely
- fashion. The application uses the function DosMonClose to terminate its
- connection with the device driver, after which it is removed from the
- data stream.
-
- From the driver's point of view, it 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 may choose to create the monitor chain
- during its initialization or wait until there is a need for it. The
- driver is informed that an application is attempting to register as a
- monitor by the receipt of a special IOCTL request packet (Category 0AH
- Function 40H); 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.
-
- Once the monitor chain is created, the driver must pass each character it
- receives from the device through the chain by calling the DevHlp service
- MonWrite. A notification routine within the driver is called by the
- kernel's monitor dispatcher when data emerges from the end of the monitor
- chain. The driver passes the data onward to the kernel in the usual
- manner. It is then distributed to applications via the API function
- DosRead.
-
- When the driver is informed by the kernel that an application has issued
- DosMonClose, the driver calls the DevHlp service DeRegister with the
- application's PID, resulting in the monitor dispatcher removing the
- application's buffers from the monitor chain. When the driver notes that
- all monitor connections are closed, it can call the DevHlp MonitorCreate
- again with a parameter that causes the monitor chain to be destroyed.
-
-
- Other Services
-
- The DevHlp services listed under semaphore management in Figure 7 allow a
- driver to communicate directly with an application via a system
- semaphore. Of course, a driver cannot create or open a system semaphore
- using the normal API calls, but if an application passes the handle for a
- system semaphore to the driver in an IOCTL packet, the driver can use the
- DevHlp service SemHandle at task time to obtain a new handle for the
- semaphore for use at interrupt time. The DevHlp functions SemRequest and
- SemClear, which the driver can call to obtain or release ownership of
- the semaphore, are the equivalents of the API functions DosSemRequest
- and DosSemClear.
-
- The Timer DevHlps assist a driver in performing polled I/O without
- degrading the system for devices that do not support interrupts or permit
- a driver to set 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 should be entered on each
- timer tick; this is the equivalent of chaining onto Interrupt 1CH (the
- ROM BIOS timer tick vector) under MS-DOS. The DevHlp service TickCount is
- a more general version of SetTimer; it also allows the number of ticks
- between entries to the driver's timer tick handler to be specified. The
- driver can remove its timer tick handler from the system's list of all
- such handlers by calling ResetTimer.
-
- The other device helper services which have not already been listed play
- minor roles, and are present mostly for convenience. For instance, the
- DevHlps listed under Request Packet Management in Figure 7 assist the
- driver in maintaining and sorting a linked list of request headers for
- pending I/O operations, but do not provide any functionality that the
- driver couldn't do for itself with a little extra code. Similarly, the
- DevHlps listed under Character Buffer Management provide simple support
- for a ring buffer of characters that are received or transmitted from the
- device.
-
-
- A Skeleton Device Driver
-
- As an illustration of the essential elements of an OS/2 device driver,
- TEMPLATE.ASM (see Figure 8) has the source code for a skeleton character
- driver called TEMPLATE.SYS. This driver performs no useful function; it
- simply returns a success code for all request packets that the kernel
- passes to it. However, TEMPLATE demonstrates the segment ordering and
- naming conventions that should be used in an installable driver, the use
- of API calls at initialization time, and a method by which initialization
- code and data can be discarded to minimize a driver's memory
- requirements. TEMPLATE can serve as a starting point for your own
- installable device drivers.
-
- In order to assemble and link TEMPLATE.SYS, you use the source file
- TEMPLATE.ASM, the module definition file TEMPLATE.DEF (see Figure 9),
- the Microsoft Macro Assembler, and the Microsoft Segmented Object Linker.
- Enter the commands shown in Figure 10.
-
- If the assembly and link are successful, you can copy the file
- TEMPLATE.SYS to the root directory of your boot disk, add
- DEVICE=TEMPLATE.SYS to the CONFIG.SYS file, and then restart the system
- to see the TEMPLATE driver's sign-on message.
-
-
- Summary
-
- OS/2 device drivers are architecturally and functionally far more complex
- than their MS-DOS ancestors, principally because they need to support
- bimodal operation and fully interrupt-driven I/O. This article has
- provided a brief overview of OS/2 device driver structure and
- capabilities.
-
-
- Figure 1: Structure of an OS/2 installable device driver file. OS/2 drivers
- are linked as small-model EXE files, containing one code 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 segments and
- discarded after driver initialization is completed to minimize the
- driver's memory overhead.
-
- Start ╔═════════════════════════════════╗ ──────┐
- of file║ EXE file header ║ │
- ╠═════════════════════════════════╣ │
- ║ Device driver header ║ Driver
- ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣ data
- ║ ║ segment
- ║ Resident driver data ║ │
- ║ ║ │
- ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣ ══════╡
- ║ Initialization data (discarded) ║ │
- ║ ║ │
- ╠═════════════════════════════════╣ Driver
- ║ Resident driver code ║ code
- ║ ║ segment
- End ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣ │
- of file║ Initialization code (discarded) ║ │
- ╚═════════════════════════════════╝ ──────┘
-
-
- Figure 2: OS/2 device driver header.
-
- In character device drivers, the name or units field contains the logical
- name of the device, left-justified and padded with spaces; in block device
- drivers, the first byte of the field contains the number of logical units
- supported by the driver (filled in by OS/2), followed by 7 bytes of reserved
- space.
-
- offset
- 0┌──────────────────────────────────────────┐
- │ link to next driver header, offset │
- 2├──────────────────────────────────────────┤
- │ link to next driver header, selector │
- 4├──────────────────────────────────────────┤
- │ device attribute word │
- 6├──────────────────────────────────────────┤
- │ offset, strategy entry point │
- 8├──────────────────────────────────────────┤
- │ reserved │
- 0AH├──────────────────────────────────────────┤
- │ │
- │ name or units (8 bytes) │
- │ │
- 12H├──────────────────────────────────────────┤
- │ │
- │ reserved (8 bytes) │
- │ │
- └──────────────────────────────────────────┘
-
-
- Figure 3: The device attribute word in the device driver header, which
- describes the characteristics and capabilities of the associated
- driver.
-
- ╓┌─────────┌─────────────────────────────────────────────────────────────────╖
- Bit(s) Meaning
-
- 15 = 1 if character device
- = 0 if block device
-
- 14 = 0 (reserved)
-
- 13 = 1 if non-IBM-compatible format (block devices)
- = 0 if IBM-compatible format (that is, FAT ID byte is valid)
-
- 12 = 1 if device name is not to be protected by "sharer" module
- (character devices only)
- Bit(s) Meaning
- (character devices only)
- = 0 if sharer controls contention for the device
-
- 11 = 1 if Device Open/Close functions (character devices) or
- Removable Media function (block devices) supported
- = 0 if Device Open/Close/Removable Media not supported
-
- 10 = 0 (reserved)
-
- 7-9 = function level, 001 to indicate OS/2 device driver
-
- 4-6 = 0 (reserved)
-
- 3 = 1 if current CLOCK device
-
- 2 = 1 if current NUL device
-
- 1 = 1 if current standard output device (stdout)
-
- 0 = 1 if current standard input device (stdin)
- Bit(s) Meaning
- 0 = 1 if current standard input device (stdin)
-
-
-
- Figure 4: A prototype OS/2 device driver request header.
-
- The OS/2 kernel requexts a driver operation by passing the bimodal address
- of a request header containing operation-specific parameters, and the driver
- communicates the results of the operation back to the kernel by placing
- status and other information in the same request header. In order to support
- multiple concurrent I/O requests, the driver maintains a private queue of
- request headers for its pending operations. This is done with the aid of the
- kernel DevHlp functions.
-
- 0┌──────────────────────────────────────────┐
- │ length of request header │
- 1├──────────────────────────────────────────┤
- │ block device unit number │
- 2├──────────────────────────────────────────┤
- │ command code (driver subfunction) │
- 3├──────────────────────────────────────────┤
- │ returned status │
- │ │
- 5├──────────────────────────────────────────┤
- │ reserved area (4 bytes) │
- │ │
- 9├──────────────────────────────────────────┤
- │ queue linkage (4 bytes) │
- │ │
- 0DH├──────────────────────────────────────────┤
- │ │
- │ command-specific data (length varies) │
- │ │
- └──────────────────────────────────────────┘
-
-
- Figure 5: Command Codes and Their Associated Driver Functions
-
- ╓┌────────┌──────────────────┌───────────────────────────────────────────────
- Command
- Code Subfunction Name Subfunction Action
- Command
- Code Subfunction Name Subfunction Action
-
- 0 Init Initialize driver (called at driver load time).
- Tests for existence and operability of
- peripheral device register interrupt handler and
- returns size of resident code and data segments
- to kernel. Block drivers also return number of
- units supported and an array of pointers to BIOS
- Parameter Blocks (BPBs).
-
- 1 Media Check Returns a code indicating whether the media has
- been changed.
-
- 2 Build BPB Returns a pointer to the BIOS Parameter Block
- for the indicated disk type. The BPB for a disk
- describes its physical characteristics (sector
- size, number of tracks, tracks per sector, and
- so on).
-
- 3 Reserved
- Command
- Code Subfunction Name Subfunction Action
- 3 Reserved
-
- 4 Read Transfers data from the device to memory.
-
- 5 NonDestructive Returns the next character from the device, if
- Read one is available, without removing it from the
- input buffer.
-
- 6 Input Status Returns a flag indicating whether characters are
- waiting in the device's input buffer.
-
- 7 Input Flush Discards all data waiting in the device's input
- buffer and cancels pending read requests.
-
- 8 Write Transfers data from memory to the device.
-
- 9 Write with Verify Transfers data from memory to the device,
- performing a read-after-write verification if
- possible.
- Command
- Code Subfunction Name Subfunction Action
- possible.
-
- 10 Output Status Returns a flag indicating whether characters are
- waiting in the device's output buffer.
-
- 11 Output Flush Discards all data waiting in the device's output
- buffer and cancels pending write requests.
-
- 12 Reserved
-
- 13 Device Open Initializes a character device for subsequent
- I/O if necessary.
-
- 14 Device Close Performs any necessary post-I/O processing (such
- as transmitting a form feed to a list device).
-
- 15 Removable Media Returns a code indicating whether the media is
- removable.
-
- Command
- Code Subfunction Name Subfunction Action
- 16 Generic IOCTL General-purpose mechanism for communication
- between the driver and application programs.
-
- 17 Reset Media Called by the kernel to acknowledge and clear
- the "uncertain media" error condition previously
- returned by the driver.
-
- 18 Get Logical Drive Returns the drive code for the logical unit
- Map currently mapped to the specified physical unit.
-
- 19 Set Logical Drive Notifies the driver which logical unit will be
- Map used for the next access to the specified
- physical unit.
-
- 20 DeInstall Driver Signals the driver to release its interrupt
- vectors and memory. Called when a character
- device driver is superseded by the loading of
- another driver with the same logical device
- Command
- Code Subfunction Name Subfunction Action
- another driver with the same logical device
- name.
-
- 21 Reserved
-
- 22 Partitionable Returns the number of partitionable fixed disks
- Fixed Disk supported by the driver.
-
- 23 Get Fixed Disk Returns the identities of the logical disks, if
- Map any, supported by driver on the specified hard
- disk.
-
- 24-26 Reserved
-
-
- Figure 6: Example of a DevHlp call. This code allocates 64Kb of physical
- memory above the 1Mb boundary for use by the 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 dd ? ; contains bimodal pointer
- ; to DevHlp common entry point
- ; (from Init routine)
-
- bufptr dd ? ; receives 32-bit physical
- ; address of allocated block
-
- .
- .
- .
- ; allocate 64Kb of memory
- ; above 1Mb for use by driver
-
- ; AX:BX = size in bytes
- mov ax,1 ; AX = high word
- mov bx,0 ; BX = low word
- mov dh,0 ; 0 = above 1Mb boundary
- mov dl,18h ; 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 7: DevHlp services by category, their function numbers, and the
- driver contents in which they may be used (K = kernel mode, I =
- interrupt mode, U = user mode, N = initialization mode).
-
- ╓┌────────────────────┌──────┌──┌──┌──┌──┌───────────────────────────────────╖
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
-
- Process Management
-
- DevDone 01H K I Signals that the specified operation
- is complete. Kernel unblocks any
- threads waiting for the operation.
-
- Block 04H K U Blocks the calling thread until a
- timeout occurs of thread is
- unblocked by Run.
-
- Run 05H K I U Activates a thread that was
- previously suspended by a call to
- Block.
-
- Yield 02H K Surrenders the CPU to any other
- threads of the same or higher
- priority that are ready to execute.
-
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- TCYield 03H K Similar to Yield, but allows other
- threads to execute only if they have
- a time-critical priority.
-
- Memory Management
-
- AllocPhys 18H K N Allocates a block of fixed (not
- swappable or movable) memory. Caller
- can specify whether the block should
- be above or below the 1Mb boundary.
-
- FreePhys 19H K N Releases a block of memory
- previously allocated with AllocPhys.
-
- Lock 13H K N Locks a memory segment, that is,
- flags the segment as nonmovable and
- nonswappable.
-
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- Unlock 14H K I N Unlocks a memory segment.
-
- Verify Access 27H K Verifies that a user process has
- access to a segment.
-
- PhysToVirt 15H K I N Converts a 32-bit physical (linear)
- address to a virtual address.
-
- UnPhysToVirt 32H K I N Signals that the virtual addresses
- previously obtained with PhysToVirt
- can be reused.
-
- PhysToUVirt 17H K Converts a 32-bit physical address
- to a virtual address accessed
- through the current Local Descriptor
- Table (LDT).
-
- VirtToPhys 16H K N Converts a virtual address
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- VirtToPhys 16H K N Converts a virtual address
- (segment:offset or selector:offset)
- to a 32-bit physical address.
-
- AllocateGDTSelector 2DH K N Allocate one or more GDT selectors
- for use by the driver.
-
- PhysToGDTSelector 2EH K N Map a physical address and length
- onto a GDT selector.
-
- RealToProt 2FH I Switch the CPU into protected mode.
-
- ProtToReal 30H I Switch the CPU into real mode.
-
- Hardware Management
-
- SetIRQ 1BH K N Sets the interrupt vector for the
- indicated interrupt request (IRQ)
- level to point to the driver's
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- level to point to the driver's
- interrupt handler.
-
- UnSetIRQ 1CH K I N Releases the interrupt vector for
- the specifies IRQ level.
-
- EOI 31H I N Issues an end of interrupt to the
- appropriate 8259 PIC for the
- specified IRQ level.
-
- Request Packet Management
-
- 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.
-
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- PullReqPacket 0AH K I Removes the oldest request packet
- from the driver's linked list.
-
- 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.
-
- Character Buffer Management
-
- QueueInit 0FH K I U N Establishes a ring buffer for
- storage of characters and
- initializes its pointers.
-
- QueueRead 12H K I U Returns and removes a character from
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- QueueRead 12H K I U Returns and removes a character from
- the specified buffer.
-
- QueueWrite 11H K I U Puts a character into the specified
- buffer.
-
- QueueFlush 10H K I U Resets the pointers to the specified
- buffer.
-
- Timer Management
-
- SetTimer 1DH K N Adds a timer tick handler to be
- called on every timer tick to the
- system's list of such handlers.
-
- TickCount 33H K I U N 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
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- modifies the interval at which an
- existing handler is called.
-
- ResetTimer 1EH K I N Removes a timer tick handler from
- the system's list of such handlers.
-
- Monitor Management
-
- MonitorCreate 1FH K N Creates or removes an empty monitor
- chain.
-
- Register 20H K Adds a process to a monitor chain.
-
- MonWrite 22H K I U Passes a data record to a monitor
- chain for filtering.
-
- MonFlush 23H K Removes all data from a monitor
- chain.
-
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- DeRegister 21H K Removes a jprocess from a monitor
- chain.
-
- Semaphore Management
-
- SemHandle 08H K I 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.
-
- SemClear 07H K I U Clears a semaphore, restarting any
- threads that were blocking on the
- semaphore.
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- semaphore.
-
- Miscellaneous Device Helpers
-
- SchedClockAddr 00H K N Obtains the address of the kernel's
- routine to be called on each clock
- tick. Used only by the system's
- clock driver.
-
- SendEvent 25H K I Signals the kernal that a critical
- event has been detected, such as the
- Session Manager hot key or a Ctrl-
- Break.
-
- GetDOSVar 24H K N Returns a bimodal pointer to a table
- of useful kernel addresses and
- variables.
-
- SetROMVector 1AH K N Captures an interrupt vector
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- SetROMVector 1AH K N Captures an interrupt vector
- normally used in real mode to invoke
- a ROM BIOS function, returning the
- previous contents of the vector for
- chaining.
-
- ROMCritSection 26H U Protects ROM BIOS code from
- interrupts that might cause a
- cantext switch. Called by a driver
- in user mode.
-
- ABIOS Communication Device Helpers (IBM PS/2 only)
-
- ABIOSCall 36H K I U N Invokes an Advanced BIOS (ABIOS)
- service on behalf of the driver.
-
- ABIOSCommonEntry 37H K I U N Transfers to an ABIOS common entry
- point on behalf of the driver.
-
- DevHlp DevHlp DevHlp
- Name Code K I U N Action
- GetLIDEntry 34H K N Obtains a logical ID (LID) for a
- physical device for use with
- subsequent ABIOS calls.
-
- FreeLIDEntry 35H K N Releases a Logical ID (LID) for a
- physical device.
-
-
- Figure 8: Installable Driver Template
-
- name template
- page 55,132
- title 'TEMPLATE - installable driver template'
- .286c
-
- ;
- ; TEMPLATE.ASM: A program skeleton for an installable
- ; OS/2 character device driver.
- ;
- ; The driver command code routines are stubs only and have
- ; no effect but to return a nonerror "Done" status.
- ;
- ;
- ;
-
- 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 line feed
-
-
- 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" routine entry point
- dw 0 ; (reserved)
- 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 on block device
- dw BuildBPB ; 2 = build BIOS parameter block
- dw Error ; 3 = not used
- dw Read ; 4 = read (input) from device
- dw NdRead ; 5 = nondestructive read
- dw InpStat ; 6 = return input status
- dw InpFlush ; 7 = flush device input buffers
- dw Write ; 8 = write (output) 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 PortAcc . ; 21 = reserved
- 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 Example 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 ; device driver Strategy routine,
- ; called by OS/2 kernel with
- ; ES:BX = address of 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 ; command code * 2
- ; branch to appropriate routine
- call word ptr [di+dispch]
-
- Strat2: ; store subfunction status in
- mov es:[bx+3],ax ; AX into request packet
-
- ret ; back to OS/2 kernel
-
- Strat endp
-
-
- Intr proc far ; driver Interrupt handler
-
- clc ; signal that we owned interrupt
-
- ret
-
- 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 (Input)
-
- 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 (Output)
-
- 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 Disks
- 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 in request header
-
- mov ax,8103h ; error bit + 'done' status
- ; + "Unknown Command" code
- ret
-
- Error endp
-
- Init proc near ; function 0 = Initialize Driver
-
- mov ax,es:[bx+14] ; pick up 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 ; 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
-
- mov ax,0100h ; return 'done' status
- ret
-
- Init endp
-
- _TEXT ends
-
- end
-
-
- Figure 9: Sample Module Definition File
-
- ;
- ; module definition file for
- ; TEMPLATE OS/2 character device driver
- ;
- LIBRARY TEMPLATE
- PROTMODE
-
-
- Figure 10: MASM TEMPLATE
-
- MASM TEMPLATE;
- LINK
- TEMPLATE,TEMPLATE.SYS,,DOSCALLS,TEMPLATE;
-
-
- Summary of the way Device Drivers Queue and Process I/O Requests.
-
- ┌─────────────────┐ ┌────────────────────┐
- │ OS/2 Kernal │ │ Hardware Interrupt │
- └────────┬────────┘ └──────────┬─────────┘
- ┌────────────────────────┐ ┌─────────────────────────┐
- │ ES:BX ──── I/O Request │ │ OS/2 Interrupt Manager │
- └────────────┬────────────┘ └────────────┬─────────────┘
- ┌────────────────────────┐ ┌─────────────────────────┐
- │ Device Strategy Routine │ │ Device Interrupt Routine │
- └────────────┬────────────┘ └────────────┬─────────────┘
- ┌────────────────────────┐ ┌─────────────────────────┐
- │Call DevHlp PushReqPacket│ │Service Hardware Interrupt│
- └──────┬──────────────────┘ └────────────┬─────────────┘
- ┌──────────────┐ ┌────┐ ┌───────────────────┐
- │Is Device Idle?├──│ No ├┐ │Is Request Complete │ ┌────┐
- └──────┬────────┘ └────┘│ ┌────┤ or Error ├─│ No ├┐
- ┌────┐ │ │ └────────────────────┘ └────┘│
- │ Yes │ │ ┌────┐ ┌─────────────────┐ │
- └──┬──┘ │ │ Yes ├─────│Set Packet Status│ │
- │ └─────┘ └───────┬─────────┘ │
- Call StartNextRequest │ ┌────────────────────┐ │
- │─────────────────┘ ┌───────┤ Call DevHlp DevDone │ │
- ┌──────┴─────────┐ │ └─────────────────────┘ │
- │ Far Return │ ┌────────────────┐ │
- └────────────────┘ Call StartNextRequest─────│ Far Return │───┘
- └────────────────┘
-
- StartNextRequest
- │
- ┌──────────────────────┐
- │ Call DevHlp │
- │ Pull ReqPacket │
- └──────────┬────────────┘
- ┌──────────────────────┐
- │ Start Hardware │
- │ Processing of Request │
- └──────────┬────────────┘
- ┌───────────────┐
- │ Far Return │
- └────────────────┘
-
- ████████████████████████████████████████████████████████████████████████████
-
- Exploring the Structure and Contents of the MS-DOS Object Module Format
-
- Richard Wilton
-
- A programmer writing a compiler, an assembler, or a language translator
- must know the details of object module format and processing. To take
- advantage of LIB and LINK, a language translator must construct object
- modules that conform to the format and usage conventions specified by
- Microsoft.
-
- Although some MS-DOS language translators generate executable 8086-
- family machine code directly from source code, most produce object code
- instead. Typically, a translator processes each file of source code
- individually and leaves the resulting object module in a separate file
- bearing an .OBJ extension. The source-code files themselves remain
- unchanged.
-
- After all of a program's source-code modules have been translated, the
- resulting object modules can be linked into a single executable program.
- Because object modules often represent only a portion of a complete
- program, each source-code module usually contains instructions that
- indicate how its corresponding object code is to be combined with the
- object code in other object modules when they are linked.
-
- The object code contained in each object module consists of a binary image
- of the program plus program structure information. This object code is
- not directly executable. The binary image corresponds to the executable
- code that will ultimately be loaded into memory for execution; it
- contains both machine code and program data. The program structure
- information includes descriptions of logical groupings defined in the
- source code (such as named subroutines or segments) and symbolic
- references to addresses in other object modules.
-
- The program structure information is used by a linkage editor, or
- linker, such as Microsoft LINK to edit the binary image of the program
- contained in the object module. The linker combines the binary images
- from one or more object modules into a complete executable program.
-
- The linker's output is an EXE file-a file containing machine code that can
- be loaded into RAM and executed (see Figure 1). The linker leaves intact
- all of the object modules it processes.
-
- Object code thus serves as an intermediate form for compiled programs.
- This form offers two major advantages:
-
- ■ Modular intermediate code. The use of object modules eliminates the
- overhead of repeated compilation of an entire program whenever
- changes are made to parts of its source code. Instead, only those
- object modules affected by source-code revisions need be
- recompiled.
-
- ■ Shareable format. Object module format is well defined, so object
- modules can be linked even if they were produced by different
- translators. Many high-level-language (HLL) compilers take advantage
- of this commonality of object-code format to support
- "interlanguage" linkage.
-
-
- Object Module Contents
-
- Object modules contain five basic types of information. Some of this
- information exists explicitly in the source code (and is subsequently
- passed on to the object module), but much is inferred by the program
- translator from the structure of the source code and the way memory is
- accessed by the 8086.
-
- Binary image. As described earlier, the binary image comprises executable
- code (such as opcodes and addresses) and program data. When object
- modules are linked, the linker builds an executable program from the
- binary image in each object module it processes. The binary image in each
- object module is always associated with program structure information
- that tells the linker how to combine it with related binary images in
- other object modules.
-
- External references. Since an object module generally represents only a
- small portion of a larger program that will be constructed from several
- object modules, it usually contains symbols that permit it to be linked
- to the other modules. Such references to corresponding symbols in
- other object modules are resolved when the modules are linked.
-
- For example, consider the following short C program:
-
- main()
- {
- puts("Hello, world\n");
- }
-
- This program calls the C function puts() to display a character string,
- but puts() is not defined in the source code. Rather, the name puts is a
- reference to a function that is external to the program's main()
- routine. When the C compiler generates an object module for this program,
- it will identify puts as an external reference. Later, the linker will
- resolve the external reference by linking the object module containing the
- puts() routine with the module containing the main() routine.
-
- Address references. When a program is built from a group of object
- modules, the actual values of many addresses cannot be computed until
- the linker combines the binary image of executable code and the program
- data from each of the program's constituent object modules. Object modules
- contain information that tells the linker how to resolve the values of
- such addresses, either symbolically (as in the case of external
- references) or relatively, in terms of some other address (such as the
- beginning of a block of executable code or program data).
-
- Debugging information. An object module can also contain information that
- relates addresses in the executable program to the corresponding source
- code. After the linker performs its address fixups, it can use the object
- module's debugging information to relate a line of source code in a
- program module to the executable code that corresponds to it.
-
- Miscellaneous information. Finally, an object module can contain comments,
- lists of symbols defined in or referenced by the module, module
- identification information, and information for use by an object
- library manager or a linker (for example, the names of object libraries
- to be searched by default).
-
-
- Terminology
-
- When the linker generates an executable program, it organizes the
- structural components of the program according to the information
- contained in the object modules. The layout of the executable program can
- be conceptually described as a run-time memory map after it has been
- loaded into memory.
-
- The basic structure of every executable program for the 8086 family of
- microprocessors must conform to the segmented architecture of the
- microprocessor. Thus, the run-time memory map of an executable program
- is partitioned into segments, each of which can be addressed by using one
- of the microprocessor's segment registers. This segmented structure of
- 8086-based programs is the basis for most of the following
- terminology.
-
- Frames. The memory address space of the 8086 is conceptually divided into
- a sequence of paragraph-aligned, overlapping 64Kb regions called frames.
- Frame 0 in the 8086's address space is the 64Kb of memory starting at
- physical address 00000H (0000:0000 in segment:offset notation), frame 1 is
- the 64Kb of memory starting at 00010H (0001:0000), and so forth. A frame
- number thus denotes the beginning of any paragraph-aligned 64Kb of
- memory. For example, the location of a 64Kb buffer that starts at address
- B800:0000 can be specified as frame 0B800H.
-
- Logical segments. The run-time memory map for every 8086 program is
- partitioned into one or more logical segments, which are groupings of
- logically related portions of the program. Typically, an MS-DOS program
- includes at least one code segment (that contains all of the program's
- executable code), one or more data segments (that contain program data),
- and one stack segment.
-
- When a program is loaded into RAM to be executed, each logical segment in
- the program can be addressed with a frame number──that is, a physical 8086
- segment address. Before the MS-DOS loader transfers control to a program
- in memory, it initializes the CS and SS registers with the segment
- addresses of the program's executable code and stack segments. If an
- MS-DOS program has a separate logical segment for program data, the
- program itself usually stores this segment's address in the DS register.
-
- Relocatable segments. In MS-DOS programs, most logical segments are
- relocatable. The loader determines the physical addresses of a program's
- relocatable segments when it places the program into memory to be
- executed. However, this address determination poses a problem for the MS-
- DOS loader, because a program may contain references to the address of a
- relocatable segment even though the address value is not determined until
- the program is loaded.
-
- The problem is solved by indicating where such references occur within
- the program's object modules. The linker then extracts this information
- from the object modules and uses it to build a list of such address
- references into a segment relocation table in the header of executable
- files. After the loader copies a program into memory for execution, it
- uses the segment relocation table to update, or fix up, the segment
- address references within the program.
-
- Consider the example in Figure 2, in which a program loads the starting
- addresses of two data segments into the DS and ES segment registers.
-
- The actual addresses of the _DATA and FAR_DATA segments are unknown when
- the source code is assembled and the corresponding object module is
- constructed. The assembler indicates this by including segment fixup
- information, instead of actual segment addresses, in the program's
- object module. When the object module is linked, the linker builds this
- segment fixup information into the segment relocation table in the
- header of the program's EXE file. Then, when the EXE file is loaded, the
- MS-DOS loader uses the information in the EXE file's header to patch the
- actual address values into the program.
-
- Absolute segments. Sometimes a program needs to address a predetermined
- segment of memory. In this case, the program's source code must declare
- an absolute segment so that a reference to the corresponding frame number
- can be built into the program's object module.
-
- For example, a program might need to address a video display buffer
- located at a specific physical address. The following assembler
- directive declares the name of the segment and its frame number:
-
- VideoBufferSegSEGMENT at 0B800h
-
- Segment alignment. When a program is loaded, the physical address of each
- logical segment is constrained by the segment's alignment. A segment can
- be page aligned (aligned on a 256-byte boundary), paragraph aligned
- (aligned on a 16-byte paragraph boundary), word aligned (aligned on an
- even-byte boundary), or byte aligned (not aligned on any particular
- boundary). A specification of each segment's alignment is part of every
- object module's program structure information.
-
- High-level-language translators generally align segments according to
- the type of data they contain. For example, executable code segments are
- usually byte aligned; program data segments are usually word aligned. With
- an assembler, segment alignment can be specified with the SEGMENT
- directive and the assembler will build this information into the
- program's object module.
-
- Concatenated segments. The linker can concatenate logical segments from
- different object modules when it builds the executable program. For
- example, several object modules may each contain part of a program's
- executable code. When the linker processes these object modules, it can
- concatenate the executable code from the different object modules into
- one range of contiguous addresses.
-
- The order in which the linker concatenates logical segments in the
- executable program is determined by the order in which the linker
- processes its input files and by the program structure information in the
- object modules. With a high-level-language translator, the translator
- infers which segments can be concatenated from the structure of the source
- code and proceeds to build appropriate segment concatenation information
- into the object modules it generates. With an assembler, the segment class
- type can be used to indicate which segments can be concatenated.
-
- Groups of segments. Segments with different names may also be grouped
- together by the linker so that they can all be addressed within the same
- 64Kb frame, even though they are not concatenated. For example, it might
- be desirable to group program data segments and a stack segment within the
- same 64Kb frame so that program data items and data on the stack can be
- addressed with the same 8086 segment register.
-
- In high-level languages, it is up to the translator to incorporate
- appropriate segment grouping information into the object modules it
- generates. With an assembler, groups of segments can be declared with the
- GROUP directive.
-
- Fixups. Sometimes a compiler or an assembler encounters addresses whose
- values cannot be determined from the source code. The addresses of
- external symbols are an obvious example. The addresses of relocatable
- segments and of labels within those segments are another example.
-
- A fixup is a language translator's way of passing the buck about such
- addresses to the linker. Typically, a translator builds a zero value in
- the binary image at locations where it cannot store an actual address.
- Accompanying each such location is fixup information, which allows the
- linker to determine the correct address. The linker then completes the
- fixup by calculating the correct address value and adding it to the value
- in the corresponding location in the binary image.
-
- The only fixups the linker cannot fully resolve are those that refer to
- the segment address of a relocatable segment. Such addresses are not known
- until the program is actually loaded, so the linker, in turn, passes the
- responsibility to the MS-DOS loader by creating a segment relocation table
- in the header of the executable file.
-
- To process fixups properly, the linker needs three pieces of information:
- the LOCATION of the value in the object module, the nature of the TARGET
- (the address whose value is not yet known), and the FRAME in which the
- address calculations are to take place. Object modules contain the
- LOCATION, TARGET, and FRAME information the linker uses to calculate
- the appropriate address for any given fixup.
-
- Consider the program in Figure 3. The statement:
-
- start: call far ptr FarProc
-
- contains a reference to an address in the logical segment FarSeg2. Because
- the assembler does not know the address of FarSeg2, it places fixup
- information about the address into the object module. The LOCATION to
- be fixed up is 1 byte past the label start (the 4-byte pointer following
- the call opcode 9AH). The TARGET is the address referenced in the call
- instruction-that is, the label FarProc in the segment FarSeg2. The FRAME
- to which the fixup relates is designated by the group FarGroup and is
- inferred from the statement
-
- ASSUME cs:FarGroup in the FarSeg2 segment.
-
- There are several different ways for a language translator to identify a
- fixup. For example, the LOCATION might be a single byte, a 16-bit offset,
- or a 32-bit pointer, as in Figure 3. The TARGET might be a label whose
- offset is relative either to the base (beginning) of a particular
- segment or to the actual LOCATION. The FRAME might be a relocatable
- segment, an absolute segment, or a group of segments.
-
- Taken together, all the information in an object module that concerns
- the alignment and grouping of segments can be regarded as a specification
- of a program's run-time memory map. In effect, the object module
- specifies what goes where in memory when a program is loaded. The
- linker can then take the program structure information in the object
- modules and generate a file containing an executable program with the
- corresponding structure.
-
-
- Structure
-
- Although object modules contain the information that ultimately
- determines the structure of an executable program, they bear little
- structural resemblance to the resulting executable program. Each object
- module is made up of a sequence of variable-length object records.
- Different types of object records contain different types of program
- information.
-
- Each object record begins with a 1-byte field that identifies its type.
- This is followed by a 2-byte field containing the length (in bytes) of the
- remainder of the record. Next comes the actual structural or program
- information, represented in one or more fields of varied lengths.
- Finally, each record ends with a 1-byte checksum.
-
- The sequence in which object records appear in an object module is
- important. Because the records vary in length, each object module must be
- constructed linearly, from start to end. More important, however, is
- the fact that some types of object records contain references to
- preceding object records. Because the linker processes object records
- sequentially, the position of each object record within an object module
- depends primarily on the type of information each record contains.
-
-
- Object Record Types
-
- Microsoft(R) LINK currently recognizes 14 types of object records, each one
- of which carries a specific type of information within the object
- module. Each type of object record is assigned an identifying six-letter
- abbreviation, but these abbreviations are used only in documentation,
- not within an object module itself. As already mentioned, the first byte
- of each object record contains a value that indicates its type. In a
- hexadecimal dump of the contents of an object module, these identifying
- bytes identify the start of each object record.
-
- Figure 4 lists the types of object records supported by LINK. The value
- of each record's identifying byte (in hexadecimal) is included, along
- with the six-letter abbreviation and a brief functional description. The
- functions of the 14 types of object records fall into six general
- categories:
-
- ■ Binary data (executable code and program data) is contained in the
- LEDATA and LIDATA records.
-
- ■ Address binding and relocation information is contained in FIXUPP
- records.
-
- ■ The structure of the run-time memory map is indicated by the SEGDEF,
- GRPDEF, COMDEF, and TYPDEF records.
-
- ■ Symbol names are declared in LNAMES, EXTDEF, and PUBDEF records.
-
- ■ Debugging information is in the LINNUM record.
-
- ■ Finally, the structure of the object module itself is determined by
- the THEADR, COMENT, and MODEND records.
-
-
- Object Record Order
-
- The sequence in which the types of object records appear in an object
- module is fairly flexible in some respects. Several record types are
- optional, and if the type of information they carry is unnecessary, they
- are omitted from an object module. In addition, most object record types
- can occur more than once in the same object module. And, because object
- records are variable in length, it is often possible to choose, as a
- matter of convenience, between combining information into one large
- record or breaking it down into several smaller records of the same
- type.
-
- As stated previously, one important constraint on the order in which
- object records appear is the need for some types of object records to
- refer to information contained in other records. Because the linker
- processes the records sequentially, object records containing such
- information must precede the records that refer to it. For instance, two
- types of object records, SEGDEF and GRPDEF, refer to the names contained
- in an LNAMES record. Thus, an LNAMES record must appear before a SEGDEF
- or GRPDEF record that refers to it so that the names in the LNAMES record
- are known to the linker by the time it processes the SEGDEF or GRPDEF
- records.
-
-
- References Between Object Records
-
- Object records can refer to information in other records either
- indirectly, by means of implicit references, or directly, by means of
- indexed references to names or other records.
-
- Implicit references. Some types of object records implicitly reference
- another record in the same object module. The most important example of
- such implicit referencing is in the FIXUPP record, which always contains
- fixup information for the preceding LEDATA or LIDATA record in the
- object module. Any time an LEDATA or LIDATA record contains a value that
- needs to be fixed up, the next record in the object module is always a
- FIXUPP record containing the actual fixup information.
-
- Indexed references to names. An object record that refers to a symbolic
- name, such as the name of a segment or an external routine, uses an index
- into a list of names contained in a previous object record. The first name
- in such a list has the index number 1, the second name has index number 2,
- the third has index number 3, and so on. Altogether, a list of as many as
- 32,767 (7FFFH) names can be incorporated into an object module-generally
- adequate for even the most verbose programmer. (LINK does, however,
- impose its own version-specific limits.)
-
- Indexed references to object records. An object record can also refer to a
- previous object record by using the same type of index. In this case, the
- index number refers to one of a list of object records of a particular
- type. For example, a FIXUPP record might refer to a segment by referencing
- one of several preceding SEGDEF records in the object module. In that
- case, a value of 1 would indicate the first SEGDEF record in the object
- module, a value of 2 would indicate the second, and so on.
-
- The index-number field in an object record can be either 1 or 2 bytes
- long. If the number is in the range 0-7FH, the high-order bit (bit 7) is 0
- and the low-order 7 bits contain the index number, so the field is only 1
- byte long (see Figure 5).
-
- If the index number is in the range 80-7FFFH, the field is 2 bytes long.
- The high-order bit of the first byte in the field is set to 1, and the
- high-order byte of the index number (which must be in the range 07FH) fits
- in the remaining 7 bits. The low-order byte of the index number is
- specified in the second byte of the field (see Figure 6). The same
- format is used whether an index refers to a list of names or to a previous
- object record.
-
-
- Microsoft 8086 Object Record Formats
-
- Just as the design of the Intel(R) 8086 microprocessor reflects the design
- of its 8-bit predecessors, 8086 object record formats are reminiscent
- of the 8-bit software tradition. In 8-bit systems, disk space and RAM
- were often at a premium. To minimize the space consumed by object
- records, information is packed into bit fields within bytes and variable-
- length fields are frequently used.
-
- Microsoft LINK recognizes a major subset of Intel's original 8086 object
- module specification (Intel Technical Specification 121748-001).
- Intel also proposed a six-letter name for each type of object record and
- symbolic names for fields. These names are documented in The MS-DOS
- Encyclopedia and appear in the order shown earlier in Figure 4.
-
- The Intel record types that are not recognized by LINK provide
- information about an executable program that MS-DOS obtains in other
- ways. For example, information about run-time overlays is supplied in
- LINK's command line rather than being encoded in object records. (Because
- they are ignored by LINK, they are not included in the Encyclopedia.)
-
- All 8086 object records conform to the format shown in Figure 7.
-
- The record type field is a 1-byte field containing the hexadecimal
- number that identifies the type of object record (see Figure 4).
-
- The record length is a 2-byte field that gives the length of the remainder
- of the object record in bytes (not including the bytes in the record type
- and record length fields). The record length is stored with the low-order
- byte first.
-
- The body field of the record varies in size and content, depending on the
- record type.
-
- The checksum is a 1-byte field that contains the negative sum (modulo 256)
- of all other bytes in the record. In other words, the checksum byte is
- calculated so that the low-order byte of the sum of all the bytes in the
- record, including the checksum byte, equals zero.
-
-
- Figure 1: Generation of an executable (EXE) file
-
- ╔═════════════════╗
- ║ Source ║
- ║ Code ║
- ╚════════╦════════╝
- ┌─────────╨─────────┐
- │Language translator│
- │ or assembler │
- └─────────╥─────────┘
-
- ╔═════════════════╗ ┌──────────────┐ ╔═════════════════╗
- ║ Object module ║═══╡Object module ╞════║ Object library ║
- ║ (OBJ file) ║ │librarian(LIB)│ ║ (LIB file) ║
- ╚════════╦════════╝ └──────────────┘ ╚════════╦════════╝
- ╚════════════════════╦══════════════════════╝
- Linker (LINK)
- ║
-
- ╔═════════════════╗
- ║Executable binary║
- ║ image (EXE file)║
- ╚════════╦════════╝
- MS-DOS loader
-
- Program runs
-
-
- Figure 2: Obtaining Data Segment Starting Addresses
-
- mov ax,seg _DATA
- mov ds,ax ; make _DATA segment
- ; addressable through DS
- mov ax,seg FAR_DATA
- mov es,ax ; make FAR_DATA segment
- ; addressable through ES
-
-
- Figure 3: A sample "program" containing statements from which the
- assembler derives fixup information.
-
- title fixups
-
- FarGroup GROUP FarSeg1, FarSeg2
-
- 0000 CodeSeg SEGMENT byte public 'CODE'
- ASSUME cs:CodeSeg
-
- 0000 9A 0000 -- R start: call far ptr FarProc
-
- 0005 CodeSeg ENDS
-
-
- 0000 FarSeg1 SEGMENT byte public ; part of FarGroup
-
- 0000 FarSeg1 ENDS
-
- 0000 FarSeg2 SEGMENT byte public
- ASSUME cs:FarGroup
-
- 0000 FarProc PROC far
- 0000 CB ret ; a FAR return
-
- FarProc ENDP
-
- 0001 FarSeg2 ENDS
-
- END
-
-
- Figure 4: Types of 8086 Object Records Supported by Microsoft LINK
-
- ID byte Abbreviation Description
-
- 80H THEADR Translator Header Record
- 88H COMENT Comment Record
- 8AH MODEND Module End Record
- 8CH EXTDEF External Names Definition Record
- 8EH TYPDEF Type Definition Record
- 90H PUBDEF Public Names Definition Record
- 94H LINNUM Line Number Record
- 96H LNAMES List of Names Record
- 98H SEGDEF Segment Definition Record
- 9AH GRPDEF Group Definition Record
- 9CH FIXUPP Fixup Record
- A0H LEDATA Logical Enumerated Data Record
- A2H LIDATA Logical Iterated Data Record
- B0H COMDEF Communal Names Definition Record
-
-
- Figure 5: One-byte Index Number.
-
- ┌────────────────────────────────────────────────────┐
- │ │█
- │ bit 7 6 5 4 3 2 1 0 │█
- │ ╔═══╦══════════════════════════════════╗ │█
- │ ║ 0 ║ Index Number ║ │█
- │ ╚═══╩══════════════════════════════════╝ │█
- │ │█
- └────────────────────────────────────────────────────┘█
- ████████████████████████████████████████████████████
-
-
- Figure 6: One-byte Index Number.
-
- bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
- ╔══╦══════════════════════════╦═══════════════════════════════╗
- ║ 1║ High-Order Byte ║ Low-Order Byte ║
- ╚══╩══════════════════════════╩═══════════════════════════════╝
- First byte Second byte
-
-
- Figure 7: 8086 Object Record Format
-
- ┌───────────────────────────────────────────────┐
- │ │█
- │ ╔══════╦═════╦═════╦════/═════╦═══════╗ │█
- │ ║Record║ Record ║ Body ║ Chk ║ │█
- │ ║ type ║ length ║ ║ sum ║ │█
- │ ╚══════╩═════╩═════╩════/═════╩═══════╝ │█
- │ │█
- └───────────────────────────────────────────────┘█
- ████████████████████████████████████████████████
-
- ████████████████████████████████████████████████████████████████████████████
-
- A Guide to Program Editors, the Developer's Most Important Tool
-
- ───────────────────────────────────────────────────────────────────────────
- Also see the following related articles:
- The Brief Editor
- A BRIEF Journey
- The ME Text Editor
- Inside ME
- A Guide to Developers
- ───────────────────────────────────────────────────────────────────────────
-
- Tony Rizzo
-
- The personal computing platform has evolved into a serious and important
- programming environment. Whenever one thinks of a programming environment,
- what usually comes to mind is the compiling or assembling of source code
- and the tools associated with that process, namely compilers and
- assemblers.
-
- However, compilers and assemblers are the very last links in a long chain
- of events and may represent only a small fraction of a program's
- development cycle. Throughout this cycle, from concept to design to the
- actual building of a program, runs a fundamental thread──the evolution of
- the text that is the source code of a program. What the professional
- programmer really spends most of his or her time doing is writing and
- editing the source code by working with a text editor.
-
- In the early days of personal computing, many programmers used word
- processors. WordStar(R), for example, often doubled as a text editor,
- providing the ability to create a "text" file by stripping any special
- codes prior to actually writing out the file. It was the first real text
- manipulation program available for the PC, and there are many
- programmers who still refuse to consider any other alternative.
-
- WordStar continues to influence even the newest compilers as evidenced
- by the emergence of the integrated editor/compiler environment. These
- compilers provide a WordStar-compatible (or keystroke-compatible)
- integrated editor.
-
- The PC software business has grown at a tremendous rate, and the
- "garage/home-brewed" software of yesterday has in many cases given way to
- sophisticated software teams working in highly competitive and fast-
- paced environments. A major factor in a programmer's success is
- productivity.
-
- New and faster compilers, assemblers, and debuggers are emerging all the
- time. Compilers now offer sophisticated code optimization capabilities,
- and tools such as CodeView(R) provide sophisticated debugging aid. But such
- tools are of little use without source code. The key to enhancing
- productivity is the ability to create well-designed source code in the
- least possible time. Consequently, WordStar no longer meets the
- strenuous demands of the professional PC programmer.
-
- There is a wide variety of editors available to programmers, ranging in
- price from as little as $40 to more than $300. Some editors are based on
- mainframe and minicomputer predecessors. Some were specifically
- developed to operate within the PC environment. Some label themselves as
- intended strictly for the professional programmer. Some purport to be all
- things to all people. Whatever their individual heritage or claim to fame
- might be, there are enough options that a programmer might have
- difficulty in choosing one editor.
-
- The technological advancements of recent generations of PCs have brought
- us faster, more sophisticated hardware capabilities, particularly in
- graphics. Each editor offers a unique set of features──multiple windows,
- EGA support, configurable keyboards, built-in macro languages, and the
- ability to compile a program using the programmer's preferred compiler or
- assembler on the fly from within the editor itself.
-
- Selecting the "right" editor is a very personal decision. How many times
- have you heard a fellow programmer complain about his editing tools?
- Perhaps you've spent most of your professional career working on a UNIX(R)
- system, only to discover that Emacs and VI don't quite make it in the PC
- world. Perhaps you only use a mainframe-based editor because you feel safe
- in knowing how it operates, even though its performance is clearly awful
- on your AT. Or perhaps you are that WordStar person who'd rather fight
- than switch.
-
- In order to help with your decision, MSJ set out to do a comprehensive
- review of all the commercially available and advertised program editors.
-
- We settled on twelve analysis areas by which to judge each editor:
-
- ■ basic editor functions
- ■ user interface
- ■ macro capabilities
- ■ file handling
- ■ special features
- ■ support for unique language features
- ■ print capabilities
- ■ word processing features
- ■ documentation
- ■ setup/installation
- ■ help/tutorial
- ■ speed
-
- Our first objective was to analyze basic editing and productivity
- features such as invoking a compiler from within the editor, editing
- multiple files simultaneously, and multiple windows. Our survey of
- programmers' requirements also indicated the need for certain word
- processing features to create quick documentation, write notes, memos,
- and so on. The ability to wordwrap, justify, and center is also handy.
-
- Speed is important, but we weren't as interested in the raw speed
- required, for example, to go from the top to the bottom of a document as
- we were in factors such as the number of keystrokes necessary to effect
- such a move. Often the time spent or wasted in keyboard manipulations
- plays a much larger role in one's perception of a product's quickness
- than raw speed. The difference between 2 and 4 seconds is not always as
- significant as having to use six keystrokes instead of two to do
- something. Nevertheless, for those of you who value such measurements we
- have included two. These speed measurements were all recorded on an
- unmodified 8-MHz IBM(R) PC/AT(R) (model 339).
-
- The chart accompanying the reviews lists the capabilities of all twelve
- editors. We have listed the editors in order from highest rating to
- lowest. The editors that received the three highest ratings were chosen
- for more detailed review.
-
- In addition, we offered the developers the opportunity to comment on
- some of the underlying design and technology issues they had to deal
- with in order to create their editors. We also encouraged them to give us
- a glimpse into their plans for the future.
-
- What we were looking for in this search-and-discover mission was proof
- that text editors have been keeping pace with other, more heavily advertised
- systems tools. We think our results point to the best-engineered editors now
- available on the market.
-
- Editing tools may not seem as important to obtain as the latest and
- greatest optimizing compiler. However, given the capabilities of this
- new generation of editors, we strongly suggest that you seriously
- reevaluate your current text editor. See how it stacks up against the best
- editors now on the market. You may just discover that a big boost to your
- productivity, as well as to your competitiveness as a programmer, awaits
- you.
-
- Microsoft Systems Journal believes strongly in the success of well-
- engineered products, whether they are Microsoft's or another vendor's. The
- three text editors that received the highest ratings are highly
- recommended──they clearly deserve your attention.
-
- There was one big surprise among the top three editors. Magma Systems' ME
- retails for all of $39.95. What can one say about such a discovery? The
- reviewer has considerable programming experience and he was very impressed,
- enough to consider it superior to other editors costing many times its
- price.
-
- Microsoft feels that OS/2 is going to become the operating system of
- choice for the majority of PCs over the next several years and is the
- ideal platform for the development of the next generation of applications.
- Is there an editor out there that will not only keep you competitive today
- but also in the protected-mode operating system world of tomorrow? Read on
- and find out.
-
-
- ───────────────────────────────────────────────────────────────────────────
- The BRIEF Editor
- ───────────────────────────────────────────────────────────────────────────
-
- Matt Trask
-
- BRIEF(R), an acronym for "Basic Reconfigurable Interactive Editing
- Facility," is a full-featured programmer's editor for DOS-based personal
- computers. Marketed by Solution Systems(R), it is moderately priced at
- $195. Even though it is not my usual editor, BRIEF is intuitive enough
- and the on-line help is good enough that I was able to use it to write
- this review.
-
-
- Documentation
-
- The documentation consists of two wire-bound paperback manuals and a quick
- reference booklet, enclosed in the typical IBM-style slip case so that
- they can maintain their own shelf space. The User's Guide includes a
- tutorial, an alphabetical reference to the editor's commands, and an
- index. In addition there is a complete summary of messages that BRIEF can
- generate and a chapter with some common questions and answers about
- editor operation.
-
- The Macro Language Guide is a complete tutorial and reference for
- BRIEF's built-in macro capability. As in any language reference there is
- a section that defines the syntax and structure of the language. Two
- examples of real-world macros are given-a searching package and a restore
- function. The section on the restore function is an informative and
- sometimes humorous visit with one of the editor's authors, Dave Nanian,
- as he demonstrates the process of developing a macro that can restore the
- editor's state (such as open files, search pattern, search direction, and
- windows) on startup to the point where it was previously exited. This
- section is an excellent insight into program development methods for any
- new programmer as Nanian goes through the whole cycle (idea, fleshing it
- out, pseudo-code, detail work, getting stuck, maintenance) in the example.
-
- The documentation includes a card that summarizes the side effects of the
- installation procedure so that you will have an idea of what the setup
- program will or will not do to your AUTOEXEC.BAT before you run it. The
- quick reference booklet is somewhat superfluous as it is easier to use
- the on-line help than to find and page through a printed reference.
-
- Although the tutorial provided with BRIEF is not of the flashy on-line
- computer-assisted-instruction type, it is thorough and more than adequate
- to get a new user up and running in a short amount of time. The first
- forty-odd pages of the User Guide are an interactive quick-start lesson
- that goes through the few editor features needed for most purposes. This
- tutorial covers startup, shutdown, editing, cursor motion, help, undo,
- cutting, pasting, windows, and other editing functions. At the end of
- this section there is a quiz to test your comprehension of the material
- by solving a typical editing problem. The next five chapters cover BRIEF
- in greater depth, getting into theory of operation and more advanced
- functionality.
-
-
- Setup and Installation
-
- BRIEF comes with a program called SETUP.EXE that configures many of the
- editor's operating parameters. You need to run this program once before
- using the editor, but the default values are adequate for getting started.
- SETUP can be run again later to reconfigure the editor as you become more
- familiar with the available features and wish to tune them to your
- preferences.
-
- CONFIG.SY and AUTOEXEC.BAT are modified by the setup program to add
- device drivers, if needed, and set environment variables that inform the
- editor of your choices. The documentation for the AUTOEXEC changes is
- adequate in case you wish to change these by hand instead of using
- SETUP.EXE. In addition, the setup program generates and compiles a
- startup macro that contains initialization commands for screen
- colors, autosave, and other options that can be customized. You can edit
- and recompile this macro to add any other actions you desire at startup.
-
- Among the configurable parameters are display parameters (color
- preferences, cursor shape, use of 43-line mode on EGA, snow test on CGA
- adapters, and video page test), filename extensions (compiler control
- commands, word processing features and autoindentation, including
- normal, smart, and template), and safety features (enable autosave, set
- autosave timeout, use of backup files, depth of undo stack, and enable
- session save/restore). Other parameters include initial insert/overstrike
- mode, empty spaces filled with tabs or spaces, whether to use Ctrl-Z at
- end of file, keyboard autorepeat rate, default line length, case
- sensitivity during searches, and use of regular expressions in
- searches.
-
- Context-sensitive help is available during setup. The setup help has a
- message, "Press Alt-H if you need more assistance," that appears and
- flashes intermittently after a period of inactivity on the keyboard. This
- attention to detail in the user interface is indicative of the
- thoroughness of the product.
-
- The only complaints I have with the setup procedure are the assumptions
- about drives and directories. SETUP asks which disk is to be used and then
- creates a subdirectory called \BRIEF, rather than asking where the
- editor's files should be kept. It assumes that the target drive is also
- the boot drive and therefore creates and modifies AUTOEXEC.BAT and
- CONFIG.SYS in the root directory of this drive rather than finding out
- where the real copies of these files are.
-
-
- Help
-
- The context-sensitive pop-up help system is excellent. Pressing Alt-H
- brings up a help menu during text entry or editing. This menu is the
- entry into a hierarchical help window subsystem that covers such topics
- as blocks, keys and commands, and windows. After selecting one of the
- menu choices, another press of Alt-H shows which key or key combination
- performs that editor function.
-
- If you are currently in the middle of an editor function such as Alt-E
- (open and edit a new file), Alt-H goes directly to the help window that
- describes that function. There is also an on-line quick reference
- available from the help menu that lists all available editor functions
- and their current key assignments. Another menu selection accepts a
- keystroke and tells you what function is performed by that key.
-
-
- Basic Editor Functions
-
- BRIEF does almost everything imaginable in the areas of cursor motion,
- block operations, delete, copy, search-and-replace, and so on, and does
- them quite well. One powerful feature not usually found in PC editors is
- the availability of UNIX-style regular expressions for searching, in
- addition to simple string matching. For instance, the angle bracket in a
- search for "<if" causes BRIEF to find all lines beginning with "if," and a
- search for "{[Hh]i} world" will find "hi world" and "Hi world."
-
- The ability to adjust the keyboard repeat rate at setup time affects the
- cursor motion. I made the mistake of setting this at the maximum repeat
- rate for efficiency, only to watch as a repeated backspace to correct an
- error about 10 characters to the left gobbled up all the text on the line.
- I was rescued by the undo (Alt-U) capability, which is configurable as a
- stack that saves editor actions, not just deleted text as some editors do.
- This is usually set up to save the 30 most recent actions, but you can
- configure it to your liking.
-
- Word processing is not a strong point of BRIEF, but it is certainly
- adequate for writing documentation and memos. A macro package is provided
- that can be attached to a file extension such as .DOC or .TXT and
- automatically enable the available word processing features when a file
- with this extension is edited. BRIEF's features include wordwrap, margins,
- centering, and automatic paragraph reformat. If you want more, you can
- enhance the supplied source code to the word processing macros. A truly
- ambitious programmer could conceivably write a full-featured word
- processor by using nothing more than BRIEF's macro language.
-
-
- User Interface
-
- The user interface in BRIEF is more visual than other, more terminal-
- oriented editors. It takes advantage of special PC features such as color
- and line drawing characters that are usually not available in non-PC
- environments.
-
- Since every key on the keyboard is reconfigurable, you can assign the
- editing functions such as search or delete in any way that you please.
- Macros can be assigned to individual keys or can be executed by the
- Execute Command key (F10 in the default configuration). This lets you
- configure BRIEF to look like another editor more to your liking, for
- instance PC/Vi or WordStar. A package that configures BRIEF to PC/Vi's
- user interface is available from the authors. The remember and playback
- functions make it easy to record keystrokes on the fly for reuse if you
- need to repeat a sequence of actions.
-
- Screen layout is one area in which personal preference often borders on
- fanaticism. BRIEF has all the things that I like on the screen, no more
- and no less. Besides the name of the current document in the top border,
- the status line at the bottom of the screen shows the current line and
- column occupied by the cursor and the time of day. If you don't like a
- border around the screen, you can disable it at setup time. You can modify
- foreground, background, and message colors with SETUP to support whatever
- is available on your system.
-
- BRIEF uses tiled windows instead of the overlapping Macintosh-style
- windows. I generally prefer the latter, but in an editing context, a
- window is usually used when more than one file has to be visible, so this
- is not really a limitation. A 43-line display mode is available when
- running on an EGA but the tiny characters are a little tough on the eyes.
- This large screen mode would be useful if you are in an extended editing
- session that requires the use of windows, since you can make each window
- larger or have more windows.
-
- The cursor shape changes to indicate which mode (insert or overstrike) is
- currently in effect. The space past the end of the last character on a
- line is referred to as virtual space──the cursor is allowed to move there
- but it changes shape to indicate that it is in virtual space rather than
- on top of a space character.
-
- Nonstandard enhanced displays such as the WYSE(TM) 700 and Hercules(R)
- Graphics Plus are supported at their maximum resolution by device
- drivers that are installed by SETUP.
-
-
- Unique Language Features
-
- BRIEF supports autoindenting and delimiter checking for all languages.
- The C language support is quite extensive. "Smart" indenting uses a
- knowledge of the language syntax to automatically indent and outdent
- as control structures open and close. Template indenting provides for
- automatic completion of C language keywords. The keyword completion
- can be confusing; it is probably useful for a programmer who only uses
- C.
-
-
- Macros
-
- BRIEF is truly outstanding in its macro capabilities. The built-in LISP-
- like macro language is a complete general-purpose programming language.
- It covers all the features that you would expect in a language except port
- I/O and system calls. Even this is not too much of a limitation; if you
- want to write a program such as a word processor with BRIEF macros, the
- built-in "dos" macro can execute external programs that perform these
- operations. The exit codes from such programs are retrieved by BRIEF and
- are returned to the calling macro. If the external program needs to
- return data, a file can be used for communication.
-
- Rudimentary timer functionality is performed by registering a macro for
- idle time processing when the keyboard is inactive. This mechanism
- operates BRIEF's autosave feature whenever there is a period of
- keyboard inactivity. The amount of time delay before autosave starts is
- a setup option.
-
- Two sets of primitives are provided: language primitives such as if,
- while, get_parm, and editing primitives such as inq_position for cursor
- control and del_edge for window modification. These primitives are the
- equivalent of the function library that is provided with most language
- translators for linkage with your own programs. Some of the available
- functions are atoi (convert an ASCII number in a string to an integer),
- beep (sound the PC's speaker), color (set the foreground, background, and
- message colors), compress (shorten multiple white spaces to single ones),
- create_window (create an overlapping window such as that which the help
- subsystem uses), keyboard_flush (discard any pending keystrokes),
- remember (record a sequence of keystrokes to playback later), and
- write_buffer (write the current file out to disk).
-
-
- File Handling
-
- Maximum file size is limited only by the available disk space. BRIEF
- automatically pages a file to disk as needed to free up memory for
- editing. Inserting the contents of one file into another by using the
- multiple file editing commands and the block copy command is
- straightforward.
-
-
- Special Features
-
- BRIEF supports an extensive list of translators for operation within the
- editor, which includes automatic return to the source file on error with
- the cursor positioned at the cause of the error. Some of the supported
- translators are MASM, Lattice and Microsoft(R) C, dBASE(R), R-M COBOL, Lahey
- FORTRAN, Microsoft QuickBASIC, and Oregon Pascal-2. Many other compilers
- are supported, and examples show you how to integrate a compiler that is
- not supported into this environment.
-
- Multiple files can be opened at the same time with the Alt-B buffer
- command, which pops up a list of all currently open files for direct
- selection. Some or all of these files can be displayed in tiled windows
- simultaneously. I don't find windows to be particularly helpful on a
- system with a small screen such as an EGA. The Macintosh paradigm is only
- useful in that it models the way work is done by showing a pile of windows
- (tasks) currently available, but the current task is given a full-sized
- screen (or close to it) to work on. If you split an 80-column screen into
- left and right halves, enough work, such as code comments, is obscured
- that it is nearly useless. On the other hand, if you use BRIEF with a WYSE
- 700 display that supports 50-lines-by-160-columns, four full screens can
- be shown at a time.
-
- BRIEF has two features of great value: a support conference on the BIX
- network and autosave. The conference gives a user direct access to the
- program's authors, an excellent supplement to the staff support offer by
- Solution Systems, as well as a forum for macro exchange. The autosave
- feature writes a copy of the current file out to disk with the extension
- .ASV whenever the keyboard is idle and deletes this file on a normal exit
- to DOS. If there is a power failure (or you're working at home and your
- two year old discovers the Reset button), this file enables you to recover
- with no lost data.
-
-
- Speed
-
- I am accustomed to using 286-and 386-based systems where program
- performance is usually not a concern. One editor that I customarily use
- practically requires these faster systems to be useful. I installed BRIEF
- on an XT-type system to see if this was the case and found no discernible
- difference. Of course, major compute-intensive operations such as a
- large search-and-translate benefit from a faster CPU, as is true of most
- programs.
-
- The responsiveness of the keyboard and other parts of the user interface
- is crisp, with no noticeable delays.
-
-
- Print Capabilities
-
- Other than the ability to mark and print a block, there is no special
- support for printing in BRIEF. You can set right margins and use embedded
- formfeed characters for pagination. However, printer features such as
- fonts are not supported.
-
-
- Vendor Support
-
- Vendor support is very good. I had a lot of trouble getting through the
- initial installation of BRIEF due to a bug in another product that was
- installed by my AUTOEXEC.BAT. After a short description of the symptoms,
- the very sharp support person said, "Oh, you must be running xxx.COM; it
- has a bug in the use of the SI register."
-
- Instead of threatening you with imprisonment in return for no guarantee of
- product usefulness, the license agreement restricts you to using BRIEF
- on only one keyboard at a time. Solution Systems also offers a service in
- which you can have your name embedded into the code along with the serial
- number so that you can keep track of who is using your program.
-
- An OS/2 version of BRIEF is currently under development for release in
- early 1988, and BRIEF's author tells me that the current UNIX version is
- being ported to other popular UNIX environments that support Sys V.3.
-
-
- Conclusions
-
- There are a lot of things to like about BRIEF. It takes advantage of
- available PC features such as 43-line mode on the EGA. Multiple windows
- make it easy to keep a spec in view while writing new source code.
- Compilation within the editor with automatic return of the cursor to the
- error can save a lot of time waiting for printouts of compile-time
- errors. One power failure is all that it takes to appreciate autosave-the
- value is not in the saving of typing time, but in the time saved not
- having to reconstruct the thought processes behind your most recent
- changes.
-
- The one feature that makes BRIEF worth the effort of learning to use a new
- editor is its programmability. The restore macro is probably the best
- advance in productivity since editors that can operate on multiple files.
- The ability to return to your PC and invoke BRIEF such that it restores
- all opened files, cursor positions, and search strings allows you to
- casually walk away without worrying about the time it would take to
- restore your mental state when it is time to start again. If Solution
- Systems would just modify the restore macro to save multiple sessions for
- different concurrent projects, an excellent program would be even better.
-
-
- ───────────────────────────────────────────────────────────────────────────
- A BRIEF Journey
- ───────────────────────────────────────────────────────────────────────────
-
- David Nanian and Michael Strickman
-
- From the start, we envisioned that BRIEF would need to run under a number
- of different operating systems. To make this task easier, and because it's
- good programming practice, BRIEF was designed to be as modular as
- possible. We made logical divisions along functional boundaries,
- splitting the main program into a number of independently operating
- subsystems that communicated through standard function interfaces.
- While many of the data structures are shared between modules, all of the
- substructures that are related to a particular module are handled by
- that module alone.
-
- The basic modules, or subsystems include: virtual memory, windows,
- logical display (refresh), physical device, input handling, macro
- interpretation, undo, buffer modification, and command dispatch.
-
- Each of these subsystems can be replaced with functions tailored to a
- particular target system. The only requirement is that the new code must
- adhere to the original subsystem interface. In the case of operating
- system subsystem replacement, this usually means including some
- intermediate "glue" routines.
-
- Most of BRIEF's subsystems were designed to act as independently as
- possible. For example, the part of BRIEF that handles changes to files
- doesn't know anything about windows, nor does it know how the file it is
- manipulating is going to be displayed. It works purely at a stream level,
- and expects that other subsystems will provide the file's stream data and
- present the results to the user.
-
- Similarly, the refreshing subsystem doesn't really know anything about
- the physical representation of the windows that it's refreshing since
- it's completely device-independent. The subsystem only assumes that it
- is refreshing ASCII text (from the file subsystem) onto some sort of
- rectangular area that can be addressed in x,y coordinates through the
- physical device interface. The algorithm redisplays the minimum amount
- of information necessary; at the physical device level these operations
- might translate to bit BLTs on a graphics display, block moves on a
- memory-mapped CRT, or scrolling operations on a terminal.
-
- Substantial portions of the editor have been implemented in BRIEF's macro
- language. We chose the macro language over either C or Assembler for
- several reasons.
-
- First, the macros themselves are virtual, both in size and in their
- interface to BRIEF. Virtual size means that the virtual memory subsystem
- reads portions of the macro as they're needed, so the entire macro does
- not have to be in memory at one time. The virtual interface means that
- the macros communicate with a "BRIEF" machine, rather than with any
- particular operating system or hardware, so they can be ported without
- change from environment to environment.
-
- More importantly, BRIEF was designed to let the user completely customize
- the editor. We could have made the source code for the entire package
- available. Although this option would allow the most complete
- reconfiguration of the program──essentially, reconfiguration without
- limits──it also creates many problems. Configuration requires extensive
- knowledge of BRIEF's internals, making "simple" configuration all but
- impossible; technical support is more difficult; updating the package
- is extremely time-consuming; moving the configured system to other
- environments is almost impossible; and, our secrets would be available
- to all of our competitors. Clearly, this was not the way to go.
-
- Another option was to provide users with a standard "library" of editing
- functions which would, in essence, be the BRIEF subsystems. A library
- could be provided through a .LIB file, a .DLL under OS/2, or even a run-
- time interface. While a library would have made it easier to update
- users, the other problems still remained.
-
- We decided to take our virtualization philosophy a step further. Rather
- than forcing the user to deal with the specifics of the system BRIEF was
- running on, we provided the user with a set of "virtual" interfaces that
- allow complete configuration without worrying about the environment or
- specific, low-level implementation details. By writing a large portion of
- BRIEF in the macro language, we provide an opportunity for users to learn
- from the coding that we've done, and to change that code to match their
- own style.
-
- BRIEF's modular design makes it easier for us to port BRIEF to other
- environments. For example, we are currently porting BRIEF to OS/2. The
- BRIEF port consists mainly of changes allowing it to conform to the
- virtual memory capabilities of the operating system. Refreshing code,
- window management, macros, and high-level input remain largely unchanged.
- The facilities provided by BRIEF's device drivers come primarily from
- the operating system, and hence that system-specific code has been
- removed. Much of the OS/2 version of BRIEF is substantially less
- complicated than the one that runs under DOS.
-
- The only problem with the OS/2 port so far has been our dependence on the
- small programming model and the DOS memory allocator. BRIEF makes
- extensive use of DOS memory allocation blocks, and we have stayed with
- the small model (with some far routines) to get the most out of PC and XT
- computers. Under OS/2, memory allocation has changed and we can no
- longer assume that allocated blocks returned from the operating system are
- paragraph-aligned segments (which, under DOS, we could store as a 16-bit
- pointer with an implied 0 offset). Although a similar scheme could have
- been implemented under OS/2, the overhead and large number of selectors
- required made it impractical. We have converted BRIEF to a large model
- program, and a lot of time has been spent converting various pointer
- references in C and assembly language.
-
- Overall, the port to OS/2 has been greatly assisted by BRIEF's modular
- design. We anticipate that porting the program to environments such as
- the Presentation Manager, UNIX(R) (and possibly even the Macintosh(TM)) will
- be easier because of this design.
-
- ───────────────────────────────────────────────────────────────────────────
- The ME Text Editor
- ───────────────────────────────────────────────────────────────────────────
-
- Greg Comeau
-
- Editing text is an important part of program development and maintenance.
- Like most other programmers, a good portion of my time is spent using a
- text editor. The editor of choice must be powerful, customizable,
- extensible, reliable, and of course fast.
-
- The Magma Editor Version 1.31, or ME as Magma Systems calls it, stands out
- among the text editors I looked at. It supports editing of multiple
- files, full and split-screen windows (pop-up or pull-down), 43-line EGA
- mode, regular expression searches, cut-and-paste block commands, a C-like
- macro language, compiles from within the editor returning with the cursor
- on any offending lines of source, and more. Just before deadline, I
- received the latest version of ME, Version 2.0 (which as of this writing
- is in beta testing), and it's loaded with new features.
-
- ME comes with help files, a macro compiler, sample macros, and a utility
- called CTAGS in addition to the editor itself. Installation was as simple
- as inserting the distribution floppy and typing INSTALL. Magma puts
- comments in the installation script so that you can modify it in case
- you don't want to install the whole package.
-
-
- Documentation
-
- The documentation for ME is not outstanding. Most commercial software
- comes in a sturdy box containing high-quality documentation, usually in
- a slip-case binder. ME arrived on a disk packaged in a disk mailer, which
- is not exactly an overwhelming presentation.
-
- After installation, I found out that you have to print your own copy of a
- file named MANUAL if you want a hard copy of the documentation. The manual
- is very disorganized and has no index, and the command reference is
- located in a file called KEYCHART rather than in the manual itself. It was
- very hard to find sections describing certain features.
-
- However, the manual is complete, and all features of ME are described at
- least once. Examples of all commands and functions are provided; more
- complicated features had more than one example. Perhaps the best feature
- of ME's documentation is that it makes it perfectly clear from the start
- how to exit from the editor, something that some editors don't do.
-
- Although I haven't seen it, Magma Systems promises that Version 2.0 will
- have a new, bound and typeset manual.
-
-
- On-line Help
-
- Once ME is installed, you invoke it by typing ME followed by the filename.
- Then, once the program is running, you can go into an on-line help
- subsystem that is not very elaborate but does the job. The text for the
- on-line help is on disk at all times, and you can change it to suit your
- needs. Changing the text to the help system is as simple as editing or
- creating a new text file by using ME and letting ME know where the file
- is located at startup, usually in \ME\HELP. Version 2.0 will have an
- expanded help subsystem that is more readable and easier to use.
-
- The general behavior of ME is consistent and reliable. I found no
- surprises and felt comfortable with it. The Ctrl-key combinations are
- set up logically and if you don't like them they can be changed. Major
- features such as buffering, windowing, and block operations are
- complete and operate without hesitation.
-
-
- Buffers and Windows
-
- Much of ME's functionality is based on buffer and window operations. A
- buffer is an area of memory that holds data; in an editor, buffers are
- responsible for holding varying lines of text and are important tools for
- a programmer's productivity. ME supports two different types of buffers;
- the first is called a scratch (or pick) buffer, which is useful for moving
- text on the screen from one area to another or between windows. ME can
- handle up to ten scratch buffers at a time. So, for instance, you could
- delete or mark a few lines from one part of the program you are editing
- and place them somewhere else in the file. Another possibility is to add
- the same group of lines to many files, say a copyright message or some
- variable declarations, by loading each file, moving to the appropriate
- place, and inserting the scratch buffer.
-
- The second type of buffer can have files loaded into it and can be seen in
- a window, a view into a buffer displayed on your screen. Each window is
- limited to a certain number of lines ranging from 1 to 24, so you can have
- up to 24 windows active at any one time. (You can have an unlimited
- number of buffers if you use ME's macro language described below.) Since
- things can get cluttered, ME allows windows to be exploded so as to fill
- the whole screen. Other windows displayed at the time of the explosion do
- not go away; they become temporarily invisible until you shrink the
- window currently in use.
-
- ME typically loads with only one window displaying the source file that
- was given as its command-line argument. You can add extra windows by
- specifying more files on the command line or by creating new windows
- after a session has started with the split-window command. Once multiple
- windows have been established, you can delete a window, increase or
- decrease the number of lines, choose between or explode windows, and
- change the color of windows.
-
- Version 2.0 of ME will have pop-up windows for adding messages to a
- customized user interface and vertical windows for comparing source
- files.
-
-
- Macros
-
- ME supports various flavors of macros, the simplest of which is the
- keyboard macro. This macro allows you to record all your keystrokes so
- that you don't have to worry about typing them over and over if you use
- them repeatedly.
-
- Keyboard macros can also be transformed into another type of macro,
- called a named macro, if you find that you must abort the current edit
- session or find yourself creating the same keyboard macro over and over.
- Named macros allow you to create commands that the ME user interface does
- not provide directly. For instance, ME can only display 24 windows at a
- time; since there are 10 scratch buffers and one buffer per window, the
- ME interface permits a maximum of 34 buffers. Using a macro of your own
- creation you could add as many buffers as you'd like. For instance,
-
- mymacro()
- {
- int bufid;
- bufid = create_buffer("tempbuf");
- show_buffer(bufid);
- ∙
- ∙
- ∙
- close_window();
- delete_buffer(bufid);
- }
-
- creates a buffer named "tempbuf" and attaches it to a new window. It can
- then have code to process and perhaps save the data in that window; at
- some point it must close the window and delete the buffer.
-
- An excellent example of a macro is shown in Figure 1. It contains a sample
- ME macro script to compile Microsoft C Version 4.0 programs and report any
- errors found. Notice its C-like syntax and its use of variables. The
- example uses most of ME's macro language constructs including constants,
- strings, arrays, arithmetic operations, compiler directives (such as
- #include), flow-control statements (for, while, if-then-else, and switch),
- function calls, global variables, local variables, parameters, and built-
- in functions. You could change this example to conform to different
- languages or different compilers; this would usually require the
- modification of just five or six lines. The ability to code macros in a C-
- like language is a strong selling point for ME. Most other editors that
- come with a macro language usually contain some sort of LISP subset for
- this, and the result is many times more complicated to write, read, and
- debug. Because of the popularity of the C language, most programmers
- will find ME's macro language extremely easy to use.
-
- ME also supports keyboard remapping: the program keeps track of key
- values so that any keystroke can be bound to another key, an ME macro, a
- user macro, or even another keyboard map. This is useful for generating
- multiple-key command sequences, either for a new group of macros or for
- emulating other editors. An interesting new feature in Version 2.0 will
- be a "typeamatic" mode for keyboard repetition.
-
- Version 2.0 of ME includes two notable changes to the macro language. One
- is the ability to use strings as the targets of a switch/case statement,
- making the macro language more powerful and efficient. Before Version
- 2.0, a user could only execute a switch statement that evaluated to a
- numeric argument, and that ruled out all strings, including those
- containing the ASCII values for the digits 0 through 9. That meant that
- any selections performed with strings had to be done either via if
- statements or by encoding a numeric value in an array that also had a
- string counterpart and then using the array index to access the string.
- Now you can switch directly on a string as well as specify strings as the
- target constants for each respective case statement. This is a facility
- that most high-level languages cannot handle.
-
- The other major change to the macro language is that ME now allows calls
- to C functions directly from the macro language, providing for a very
- flexible interface. You can call all routines internal to ME, and now
- you can write parts of your larger or slower macros in C. Also, if you
- need to call some of the C run-time libraries, this lets you do so without
- having to create new macros. Instead, you just add an entry to a table and
- recompile ME. (This option is available only if you've purchased the
- source code for ME, which costs $95. Magma includes a README file
- suggesting the use of MSC 4.0 or higher for any compilations.)
-
- Other additions to the language include labels, the goto statement, and
- hexadecimal constants, all of which make the ME macro language much
- richer without going overboard. The end result is that macro generation is
- more readable, structured, and easier to perform when compared with most
- of the other macro-based editors on the market, which force you to use a
- terse and error-prone LISP-like language. LISP and its subsets are
- powerful, but not very productive or familiar to many programmers.
-
-
- Regular Expressions
-
- Many concepts from the UNIX operating system and its associated toolkit
- have made their way into mainstream computing in spite of their
- terseness and complexity. One such concept is regular expressions, which
- let you build very complicated pattern-matching commands using
- metacharacters to facilitate text searches. For instance, let's say you
- wanted to search for every occurrence of "abc" in the current window
- and change it to "ABC." That's simple enough with most editors. Now let's
- add a slight twist to the search: all the occurrences of "abc" must occur
- at the very beginning of a line. Most editors would fail at such a simple
- request.
-
- Looking through ME's list of metacharacters (see Figure 1 for a sample of
- the on-line menu for Search and Substitute Commands) you may notice the "
- " character. By using this special character in the previous request you
- could now look for the pattern " abc" instead of "abc."
-
- Another problem that comes up often is swapping two columns on a line or
- table. This can be accomplished by using " \(?*\) \(?*\)" as the search
- pattern and " \2 \1" as the replacement pattern. Such a feat often
- requires writing a general-purpose conversion utility, but comes naturally
- to ME.
-
-
- Programmer-specific Features
-
- For the programmer, ME has a few unique language features. One is its
- support for autoindenting while in input mode. Another useful feature
- is the match-brace command, which finds a closing curly brace or a closing
- parenthesis if the cursor is positioned at an opening one (and vice
- versa). Compile/next scenarios are supported with macros. And although
- Magma doesn't supply any language-specific syntax templates or syntax-
- based editing even on a simple level, you can certainly write macros to
- handle this. It is surprising that templates are not provided for the C
- language; when I mentioned this to the people at Magma, they said they
- were working on it and will have it in the next release of ME.
-
- Magma provides the CTAGS utility with every copy of ME. CTAGS was
- originally created for BSD UNIX and allows you to create a file containing
- a list of filenames and line numbers of every function you use in your
- code. Later, in an editing session, you can automatically create a new
- window containing the source file of any function in the list with the
- cursor properly positioned on it merely by responding to the go-to-
- function command sequence with the function name. Still, CTAGS is limited
- in that the file it creates does not change as your source code changes.
- It can be helpful in a large programming project though, especially if you
- are unfamiliar with the project.
-
- ME's command set has some unique features. The 8-bit-mode command is used
- for entering ASCII characters that are generally classified as
- unprintable or extended, that is, those with values less than 32 and
- greater than 127. Most editors allow you to enter only one such character
- at a time, and ME is no exception. However, ME also lets you enter a
- sequence of extended commands by switching into 8-bit-mode. This is very
- handy for generating terminal escape sequences or for emulating a strange
- file format.
-
- Another interesting point about ME is its source code option. Because it
- is written in C, the curious can use the source code to learn various data
- structure concepts by looking at a real-life application. Also, you can
- convert your most heavily used macros into true built-in commands by
- modifying some of ME's internals. Finally, some programmers may be
- interested in contacting Magma for an OEM license so they can include ME
- in their product line and/or applications as well as configuring it to
- their own needs.
-
- Magma is now working on an OS/2 version of ME, which will support all
- features of Version 2.0 as well as OS/2-specific concepts. One such
- planned feature is extending ME's autosave ability so that buffers get
- saved by another task. That way your edit sessions will not be interrupted
- abruptly.
-
-
- Printing
-
- ME does not support printing. If you need to print a copy of your source
- code or documentation, you must write a macro to handle it since Magma
- doesn't provide any. This was annoying since it meant getting intimate
- with ME's macro language, but it does demonstrate how powerful and
- adaptable ME can be. For instance, I wrote a macro that picked up the
- current filename and printed out a listing of the program in the buffer
- much like the UNIX pr command. It included line numbers, page breaks, and
- a header identifying the file and the page number. One could even expand
- on such a macro to accept a list of dot commands to perform such tasks as
- centering, justifying, underlining, and boldfacing.
-
-
- Vendor Support
-
- Magma's technical support is excellent. When I called them for support,
- I was always promptly greeted by a member of the staff, and my questions
- were answered eagerly and completely. Need I say more? Further, if you
- happen to be a member of BIX, you can communicate directly with Magma
- Systems on-line.
-
-
- Overall Impression
-
- ME is one of the top programmer-oriented editors on the market. It
- contains many concepts ideal for maximizing programmer productivity and
- stands out in four areas.
-
- The first area is its command set. High among its list of features are its
- various macros accessible either on the fly or via its macro language.
- This allows for custom editing and flexibility when dealing with
- complicated or repetitious tasks. Also, its use of buffering and
- windowing offers programmers access to multiple files, a necessity even
- when working on small projects.
-
- The next area of importance is ME's consistency. Its operation is reliable
- in every aspect. In general, the user interface is well thought out and
- its behavior is not erratic. Programmers do not need to spend time trying
- to fight with their tools so that they can do their editing. Also, when
- programmers do need to reconfigure part of ME's operation, it is a very
- smooth transformation.
-
- As for speed, ME performs very quickly. Most cursor operations occur
- instantaneously, and ME is usually waiting for you instead of the other
- way around. Similarly, non-window-oriented commands are also speedy,
- including searches for regular expressions and macro functions written
- by the user.
-
- Finally, ME contains features that are usually found in more expensive
- editors costing several hundred dollars, but its price tag is only
- $39.95. I'm told that the new release, Version 2.0, will retail for
- $89.95. It is surely the most economical editor allowing for such a
- powerful environment for programmers and is surely one of the bargains
- of 1988. Even at double the price, there's no question about its
- price/performance ratio.
-
- ME is fast, complete, reliable, and productive──exactly what a
- programmer's editor should be.
-
-
- ───────────────────────────────────────────────────────────────────────────
- CompuView's VEDIT PLUS
- ───────────────────────────────────────────────────────────────────────────
-
- Randall Swan
-
- VEDIT PLUS, from CompuView, is an excellent programming editor with
- many powerful options and a high degree of customization. The program is
- very quick to load, respond, and execute all commands.
-
- The commands are organized in a logical manner, and the on-line help and
- tutorials in the documentation considerably speed up the learning
- process. VEDIT enables you to edit multiple files simultaneously in
- separate screens or windows and to store text or macros in multiple text
- registers, which saves a great deal of time.
-
- VEDIT offers you a choice of command mode, in which you type specific
- commands, or visual mode, in which you use predefined function and Ctrl-
- key combinations to specify edit functions, thus providing much
- flexibility. The editor can be extensively tuned to an individual
- programmer's environment by using the command mode to write command
- sequences and storing them as keyboard macros with specially designated
- function keys and Ctrl-key combinations.
-
-
- Documentation
-
- VEDIT PLUS has fairly substantial documentation──378 pages in a 6-by-
- 81/2-inch three-ring binder. It includes a tutorial, a user guide, a
- programming guide, a section on installation, and a reference section.
-
- The tutorial covers topics ranging from simple editing operations to more
- complicated ones. There is also a 30-Minute Tutorial in the introduction
- to get started quickly. However, specific keystrokes are conspicuously
- absent from the documentation; they are described only in the on-line
- help screens. Another problem with the documentation is that it was
- written for all installations of VEDIT PLUS, including the CRT terminal
- version and various memory-mapped machine-specific versions.
-
-
- Setup
-
- To set up VEDIT PLUS, the manual tells you to simply create a VEDIT
- subdirectory and copy the one disk into it. Several things in the
- installation instructions, however, are not fully explained.
-
- An INSTALL.EXE program allows for extensive customization of VEDIT.
- From a menu in INSTALL called Main Menu Tasks, you can modify the
- keyboard layout, add keystroke macros, and change print, edit, file-
- handling, and screen display parameters. You can also change edit switch
- settings, visual settings, and the command mode interface. It is
- possible to create numerous configurations by running INSTALL
- VPLUS.COM "outfile".COM, each identified by a different sign-on message.
-
-
- On-line Help
-
- On-line help is easily accessible by pressing Alt-F1, though this
- keystroke is explained only in the introductory tutorial. The first
- screen displays all the edit functions with corresponding keystrokes, the
- next screen displays the separate default and expert keyboard layouts,
- and the third displays status line messages and editing tasks. You can
- access on-line help by entering command mode and typing EH to see a list
- of topics or H to display five screens of information: basic and text
- register commands; extended, jump, operating system, and print commands;
- miscellaneous and numeric register commands; keyboard control, command
- modifiers, help aids, generic/indirect, numeric specifiers, and
- miscellany; and numeric, relational, and logical operators and operator
- precedence.
-
- You can skip the menu by typing H followed by a command to display a
- description of that particular command, or EH followed by a task name to
- display a description of commands needed for that particular task. You
- can select an individual command or function from any of the help screens
- and obtain a detailed description by typing its name.
-
- After selecting any individual item, however, pressing any other keystroke
- returns to the text rather than back to the help screen, which makes it
- difficult to look up more than one item at a time. There is also no way to
- exit back to the editor without either selecting a particular item or
- paging through all the help screens by hitting Return. You can edit and
- expand all three help files.
-
- One small source of confusion is that the two preconfigured keyboard
- layouts, default and expert, use different keys to define the same
- functions. The expert layout offers more predefined functions. The
- explanations in the help functions depend on which layout has been
- installed. There are no pop-up or pull-down help menus, nor is the help
- context-sensitive. You cannot access the help screens in the middle of a
- command.
-
-
- Basic Editor Functions
-
- Cursor movement is performed with the standard up, down, right-, and
- left-arrow keys. The Goto function allows for cursor movement to the
- beginning or end of the file, to a specific line number, or to previously
- set markers in the text. In general, though, it is not possible to move
- the cursor to an empty part of the screen.
-
- You have a choice of three modes for cursor movement: mode 0 causes the
- cursor to move left from the end of a long line to the end of a shorter
- line; mode 1 (default) allows the cursor to move straight up and down
- regardless of line length but moves the cursor left if new text is added
- when the cursor is beyond the end of the line; and mode 2 allows straight
- up and down movement but adds new text at the cursor position, inserting
- spaces between the end of the line and the new text.
-
- VEDIT doesn't allow you to define columns, but in other respects the block
- functions are very flexible. There are 36 text registers, labeled 0 to 9
- and A to Z, into which blocks of text can be moved or copied, either
- overwriting or appending it (using a plus sign) to any text already in
- the register. However, this operation is cumbersome since the command
- must be invoked three times, with a different sequence of keystrokes each
- time.
-
- The defined block is not highlighted, so the only way to see the
- beginning of the block is to repeat the block function (F9) followed by S
- for the Swap option. Inserting a block displaces any text on that line to
- the right. There are no functions to move or copy text without defining a
- block, but the RC command copies a specific line or range of characters
- in the edit buffer to a specified register.
-
- The search-and-replace functions offer a number of options. For example,
- both the Find (F2) and Replace (Alt-F2) functions allow you to use a
- previously defined search-and-replace string and start the search at the
- beginning of the edit buffer.
-
- The default setting for search-and-replace is not case-sensitive but can
- be set to distinguish between upper- and lowercase. Pattern-matching
- codes, such as |A for any letter, can also be used in search-and-replace
- functions.
-
- You can set wordwrap in Task 5 (Change Edit Parameters) of the INSTALL
- program, using the EP command, or selecting the Word Wrap option from
- the User function to specify a right margin, or 0 for no wordwrap. The
- default value for wordwrap is 0 in the preconfigured editor version,
- which is a little disconcerting when one is first using VEDIT.
-
- Paragraphs are formatted by using Indent or Undent to specify the left
- margin, setting wordwrap and justification, and then selecting Format
- Paragraph. The indentation (tab or offset) of the first line of the
- paragraph is preserved.
-
- Automatic conversion of upper-to-lowercase and vice versa is done by
- selecting the Uc/lc option of the Misc function to convert the character
- at the cursor. CompuView provides a V-SPELL spelling checker for any
- files created with VEDIT.
-
-
- User Interface
-
- All 10 of the function keys, most of the Alt-function-key combinations,
- and some Ctrl-key combinations are defined in the two preconfigured
- keyboard layouts, default and expert. The normal displayable characters
- and the Enter key (Ctrl-M) cannot be redefined, but all of the function
- keys, sequences of Ctrl- or Alt-letter combinations, or any escape
- sequence can be redefined by running INSTALL and selecting Modify Keyboard
- Layout.
-
- The default assignments can all be cleared or retained. Each edit function
- name, followed by the current assignment, is prompted in sequence and can
- be retained as is by pressing Enter or reassigned to a different function
- key, Ctrl-key combination, or escape sequence.
-
- The normal visual edit mode displays a status line at the top of the
- screen that shows the current line number, column number, and filename.
- The status line is replaced by the options of any edit function that is
- invoked or an error message. Switching into command mode puts "Command" in
- the status line and a "Command:" prompt at the bottom of the screen.
-
- You can set the number of lines for paging and lines displayed (three by
- default) above and below the cursor as the screen scrolls, the column for
- the horizontal scroll margin, and the continuation character (the hyphen
- by default) used for automatic wordwrap when the horizontal scroll margin
- is reached to different values by running INSTALL.
-
- Reverse video on monochrome systems and the colors on CGA or EGA systems
- can be set to new defaults by running Task 10 in INSTALL (Change Screen
- Dependent Parameters) or by using the YEA command to change both the
- foreground and background colors of the current window.
-
- VEDIT supports any screen size up to 70-lines-by-250-columns. You can set
- the size by running INSTALL and changing the number of screen lines and
- length of displayed line in Task 10 and the screen line length in Task 11
- (Additional Memory Mapped Installation Features) as well as setting
- Using High Speed EGA Color Board to Yes.
-
-
- Printing
-
- VEDIT offers such standard page layout capabilities as margins, paragraph
- formatting, and justification. CompuView also markets a more
- sophisticated page formatter program called V-PRINT (not included with
- the editor) that can create headings, tables of contents, and indexes, do
- complete paragraph formatting, and interpret page formatter commands in
- VEDIT text files.
-
- You can print text by issuing a PR command, which puts the filename and
- page number at the top of each page, or by loading the PRINT.VDM macro,
- which performs simple print formatting. The only way to print line
- numbers, headers, and footers is by modifying the PRINT.VDM macro.
-
- Line numbering can be automatically added to the file by creating a
- simple command macro to set the starting line number, then creating a loop
- to insert the line number at the beginning of the line, add a specified
- increment, and find the next line. VEDIT PLUS does not support special
- features, such as different fonts.
-
-
- Unique Language Features
-
- Language-specific editing features are found under the Misc menu.
- Positioning the cursor on the braces, brackets, angle brackets, or
- parentheses and selecting the Match Parentheses option moves the cursor
- forward or backward to the matching pair, showing any syntax errors.
-
- The automatic indenting mode can be set with the ES command to enable Edit
- Switch 3 or by choosing the User function and selecting Auto-Indent. Each
- new line will automatically indent the same amount as the previous
- line.
-
-
- Macro Capabilities
-
- VEDIT macros can be stored as command macros in a text register and
- executed with the Macro function or the RL command and the M command
- followed by the register number to load and execute the macro in the
- specified register. You can build keystroke macros by running Task 3 of
- the INSTALL program or by using the Define function (Ctrl-D) and executing
- the macro by pressing the defined function or Ctrl-key combination.
-
- Text register macros are temporary unless you save the macro or add the
- sequence of commands, including a command to copy to a specific register
- to the VEDIT.INI file.
-
- A keystroke macro can be any combination of a sequence of command mode
- commands, visual mode edit functions, or keystrokes. Text register macros,
- however, can only be sequences of one or more commands. The command mode
- used with macros is, in effect, a special macro language, since a macro
- can combine any series of commands into a single function, like a small
- program.
-
- Several macros are provided with the editor and defined in the
- preconfigured default keyboard layout, and there are even more with the
- expert layout. The documentation also has several examples of command
- macros. You can define any number of additional or replacement macros and
- assign them to any function key or Ctrl- or Alt-letter combination. With
- a little effort, any user-defined macro can be added to the definitions on
- the help screens. You can also give a command macro a meaningful name and
- save it as a file with the Register Save command, but this is not
- possible with a macro that includes functions and recorded keystrokes.
-
-
- File Handling
-
- The maximum file size for editing in VEDIT is half the available disk
- space, since both an input and an output file are required. If the input
- and output files are on different drives, the maximum size is the whole
- disk. If backward disk buffering is used, the maximum size is one third
- of the disk size, since temporary equivalents to input and output files
- must be created.
-
- Automatic forward and backward disk buffering handles large files by
- reading a section of the file, called a file page, into the edit buffer,
- then writing it to an output file when it reaches the end of the edit
- buffer and reading in another file page. The size of the file page is 8Kb
- by default. Backward disk buffering reads a file page from the output
- file back into the edit buffer, writing text from the end of the edit
- buffer out to a temporary disk file; forward buffering subsequently
- reads first from the temporary file, then from the input file.
-
- You can merge files by using the EG command to specify an entire file or a
- line range to extract from one file and insert into the file being edited.
- The EL command displays the file or specific portion with line numbers. A
- section of text can be written from one file into one of the text
- registers and placed in a second file if there is not enough memory to
- reserve a 64Kb segment for EA-EZ.
-
- To create windows you split the current window in half horizontally or
- vertically. The number of possible windows is limited by the screen size,
- since the minimum window size is one line and 15 columns, plus a border,
- and there are no overlapping windows. Seventy windows are possible on a
- standard 80-column-by-24-line screen.
-
- Windows are independently configurable as to size, number of lines or
- columns, and foreground/background colors, and each is given a one-
- character name shown in the top border line and highlighted when the
- window is active.
-
- The Window function allows you to create a window, specifying top,
- bottom, left or right position, number of lines or columns, and the name
- of the window. You can also delete a window, switch between windows, and
- zoom the current window up to full screen size, which is a very nice
- feature.
-
- One helpful feature is the option of using the command mode as an on-line
- calculator for integer calculations or finding the numeric value of any
- ASCII character. The only drawback is that it cannot handle decimal or
- fractional values.
-
- VEDIT is seriously limited in that it does not provide for invoking a
- compiler from within the editor or returning to the text with the cursor
- positioned on the particular text line when a compiler error occurs.
-
-
- Conclusion
-
- VEDIT is a very good editor but I do have some complaints about it, mostly
- concerning the lack of a direct link to a compiler to run from the editor
- or return directly to error lines and certain organizational features that
- should be improved.
-
- There should only be one preconfigured keyboard layout; providing two
- different layouts is unnecessary and confusing. The functions really
- ought to be listed alphabetically for easy reference in both the help
- screens and the documentation, and specific function and Ctrl-key
- combinations should be described in the documentation as default values
- that can be changed. The command mode, though very powerful, is
- unnecessarily cryptic because the command names are often letter
- combinations that are arbitrary rather than mnemonic. The functions that
- provide one-line option menus when selected should be identified as such.
- Several of these functions, such as Misc or User, have names that give no
- indication of the options that lie beneath them.
-
- Still, VEDIT's many options- speed, ability to switch between command and
- visual modes, and the ease with which you can customize it to meet your
- own needs-make it a powerful, flexible, and efficient editor. It is
- highly recommended.
-
-
- Figure 1: This sample macro script will compile Microsoft C Version 4.0
- programs and report any errors found.
-
- /*MSCCOMP - this file contains macros for automating the compile and
- * error-search procedures under Microsoft 4.0. To compile the
- * current buffer, press Alt-C. To search for errors, press Ctrl-N.
- *
- * Written by Marc Adler of MAGMA SYSTEMS. January 1987
- */
-
- #define ALT_C 174
- #define CTRL_N 14
-
- #define NO 0
- #define YES 1
-
- int reached_eof;
-
- init()
- {
- assign_key("compile", ALT_C);
- assign_key("next_error", CTRL_N);
- }
-
- compile()
- {
- string currfile, extension;
- string errmsg;
- int i;
-
- errmsg = "Error - the file is not a C file. <ENTER to continue>";
-
- currfile = filename();
-
- /* Make sure that the current buffer is a C file */
- if ((i = index(currfile, ".")) > 0) /* get the extension */
- {
- extension = substr(currfile, i+1, 3);
- if (extension != "c" && extension != "C") /* is it a C file? */
- {
- get_tty_str(errmsg); /* NO!!! Error!!! */
- return;
- }
- }
- else /* the buffer has no extension, so it's an error */
- {
- get_tty_str(errmsg);
- return;
- }
-
- writefile(currfile); /* save the current version & compile it */
- set_buffer_modified(currbuf(), 0);
- os_command(sprintf("msc /AL /Zi /J %s; > errs", currfile));
- next_error(); /* see if there are errors */
- }
-
-
- next_error()
- {
- int id, old_id;
- int n;
- string cl, foo;
-
- old_id = currbuf(); /* save the id of the source buffer */
-
- if ((id = find_buffer("errs")) == 0)
- {
- id = create_buffer("errs");
- reached_eof = NO;
- }
- id = setcurrbuf(id);
-
- /* Microsoft C 4.0 puts out error messages in this format: */
- /* filename(line #) : messagetype error# message */
- if (reached_eof == NO && fsearch("([0-9][0-9]*) : ?*error") > 0)
- {
- n = atoi(substr(cl = currline(), currcol()+1, 5));
- /* get line # */
- gobol(); /* move to the next error */
- if (!down())
- reached_eof = YES;
- show_buffer(old_id); /* go to the source buffer and */
- goline(n); /* move to the offending line */
- message(cl); /* display the error message */
- get_tty_char(); /* and let the user acknowledge */
- }
-
- else /* Reached the end of the error file */
- {
- foo = get_tty_str("NO MORE ERRORS");
- delete_buffer(id); /* get rid of the error buffer */
- show_buffer(old_id); /* and set the current buffer */
- }
- }
-
-
- ───────────────────────────────────────────────────────────────────────────
- Inside ME
- ───────────────────────────────────────────────────────────────────────────
-
- Marc Adler
-
- ME was born out of frustration with current editing environments.
- Programmers always have a wish list of features that they would like in
- their editing environment, but which their editor does not support.
- Magma's goal was to develop an editor that offered both flexibility and
- extensibility. Users would then be able to create an environment tailor-
- made to meet their specific programming needs.
-
- The ability to reconfigure was at the top of our list when working on the
- design for a new editor. This meant the editor needed a macro language
- that would allow one to write new commands. Unfortunately, most of the
- editors that provide macro capability have LISP or TECO-like macro
- languages which are extremely cumbersome to use. The growing
- popularity of the C language led us to believe that our greatest
- audience would be C programmers, and thus we focused our attention on
- their needs. This eliminated the need to put thousands of parentheses in
- ME's macro code, as would be required by the LISP syntax. However, a macro
- language should be at a "higher level" than a traditional third
- generation programming language such as C. The programmer should not
- have to worry about pointers, addresses, and memory allocation when
- creating a macro. Therefore, we decided that our macro language would
- differ from C with respect to the ease of programmability.
-
- We also wanted the macros to run as quickly as possible. Therefore a macro
- would need to be compiled into a low-level intermediate code before being
- executed by the editor's interpreter. The intermediate code would contain
- opcodes for assignment, control flow, and procedure invocation, and would
- be low-level enough that an intermediate code file could be turned into an
- assembly language routine and linked in with the editor.
-
- Dynamic typing of variables is one feature that saves the macro
- programmer a bit of time. Variable declarations are usually at the
- programmer's discretion, but in our design the macro interpreter itself
- figures out the correct typing at run-time.
-
- String manipulation should be performed as easily as possible. ME's
- macro language relieves C programmers from constant worry about memory
- allocation when dealing with strings by performing all memory allocation
- internally.
-
-
- Linking in C Functions
-
- To expand their environments, users should have access to windowing,
- database, and possibly communications libraries. The ability to
- integrate these functions at the macro language level is very desirable.
- Unfortunately, if all of this functionality were put directly into ME's
- kernel, the resulting size would make the editor very unwieldy.
-
- The ability to call external C functions from within the macro language
- was added in version 2.0 of ME in order to make the editor truly
- reconfigurable. A table was provided to let ME users add C functions to
- the editor's database of callable routines. To add an external function to
- the macro language, two items are inserted into this table-the name of the
- function and a reference to the function. For instance, if a programmer
- wants to add a database routine called db_create(), then an entry such as
- the following is added to the table:
-
- { "create_database", db_create }
-
- After adding this entry, the table needs to be recompiled and linked into
- the editor.
-
- Whenever a programmer wants to access this function in the macro language,
- the line
-
- create_database("my_db");
-
- is coded into the macro program. The macro interpreter, upon seeing this
- function, looks at the external function table and calls the function.
-
-
- OS2 and Porting
-
- ME is written almost completely in the C ANSI standard. This allows the
- source to be compiled by any compiler supporting the ANSI standard.
-
- ME will soon support OS/2 protected mode. OS/2's Family API was designed
- so that most DOS applications could easily be ported to protected mode.
- Since ME uses the standard C memory allocation calls, one immediate
- benefit will be the increased file size that the user will be able to
- edit. At the macro language level, support will be provided for the user
- to access dynamic-link libraries.
-
-
- Future Directions
-
- Since ME is available with full source code, Magma Software Systems is
- anxious to have its users determine our future direction. Various
- applications have been written in ME's macro language, including a DBASE
- programming system, and a Computer-Aided Software Engineering (CASE)
- system. The ability to access external C functions will allow our users
- to be more creative in their applications. We will work with our users to
- insure that ME will be able to fully support their needs.
-
-
- ───────────────────────────────────────────────────────────────────────────
- DEVELOPERS
- ───────────────────────────────────────────────────────────────────────────
-
- BRIEF
- Solution Systems
- 541 Main St., Suite 410B
- South Weymouth, MA 02190
- (617) 337-6963 or (800) 821-2492
-
- Epsilon Editor
- Lugaru Software
- 5740 Darlington Rd.
- Pittsburgh, PA 15217
- (412) 421-5911
-
- KEDIT
- Mansfield Software Group, Inc.
- P.O. Box 432
- Storrs, CT 06268
- (203) 429-8402
-
- ME
- Magma Software Systems
- 138-23 Hoover Ave.
- Jamaica, NY 11435
- (201) 792-3954
-
- MICRO/SPF
- Phaser Systems, Inc.
- 115 Sansome St.
- San Francisco, CA 94104
- (415) 434-3990
-
- The Norton Editor
- Peter Norton Computing, Inc.
- 2210 Wilshire Blvd., #186
- Santa Monica, CA 90403
- (213) 453-2361
-
- PC/VI
- Custom Software Systems (CSS)
- P.O. Box 678
- Natick, MA 01760
- (617) 653-2555
-
- PMATE
- Phoenix Technologies, Ltd.
- 320 Norwood Park South
- Norwood, MA 02062
- (617) 769-8310
-
- Uni Press Emacs
- Uni Press Software
- 2025 Lincoln Highway
- Edison, NJ 08817
- (800) 222-0550
-
- VEDIT PLUS
- CompuView Products, Inc.
- 1955 Pauline Blvd.
- Ann Arbor, MI 48103
- (313) 996-1299
-
- WordPerfect Program Editor
- (part of the WordPerfect Library)
- WordPerfect Corporation
- 288 West Center St.
- Orem, Utah 84057
- (801) 226-7877
-
- XTC
- Wendin
- Box 3888
- Spokane, WA 99220-3888
- (509) 624-8088
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Vol. 3 No. 3 Table of Contents
-
- SQLWindows Brings a Graphical User Interface to SQL Database Applications
-
- Gupta Technologies, Inc., will soon release SQLWindows, an innovative
- developement system that makes it easier to create database applications
- by combining the graphic user interface of Windows with the power of
- programming in SQL. We talked with the designers of this new product.
-
-
- The Graphics Programming Interface: A Guide to OS/2 Presentation Spaces
-
- The Graphics Programming Interface (GPI) of the OS/2 Presentation Manager
- (PM) introduces many new concepts for programmers. Charles Petzold examines
- the three types of presentation space, data structures internal to the PM
- that act as a passport to using its graphics functions.
-
-
- Using OS/2 Semaphores to Coordinate Concurrent Threads of Execution
-
- OS/2 semaphores are an interprocess communication facility for coordinating
- concurrent threads of execution. They can serialize access to code and
- allow a thread to signal that an event has occured. This article looks at
- the four types of semaphores; exclusive system, nonexclusive system, RAM,
- and FSRam.
-
-
- Design Concepts and Considerations in Building an OS/2 Dynamic-Link Library
-
- Dynamic-link libraries are among the most valuable facilities of OS/2. They
- permit multiple applications to share only one copy of a function. This
- article provides programmers with a road map that demonstrates that writing
- a dynamic-link library is not as complicated as it may seem at first glance.
-
-
- New Compiler Technology Boosts Microsoft(R) QuickBASIC 4.0 Productivity
-
- Using proprietary threaded pseudocode technology, the latest version of
- Microsoft QuickBASIC offers the best features of both a compiler and an
- interpreter, eliminating the need to choose between the two. It provides
- an instant, integrated programming environment.
-
-
- Debug Microsoft(R) Windows Programs More Effectively with a Simple Utility
-
- Unfortunately, many traditional debugging techniques don't work in the
- Windows environment. The DEBUG utility provides print statementlike
- debugging within Windows. The utility will also record and display
- internal program events and lets you manage the process with a central
- control panel.
-
-
- An Examination of the Operating Principles of the Microsoft Object Linker
-
- The Microsoft Object Linker (LINK) is a powerful development tool that
- supports segment ordering, run-time memory management, and dynamic overlays.
- We discuss the details of how LINK works to resolve memory references for
- more efficient and simpler programs.
-
-
- EDITOR'S NOTE
-
- This issue's lead article takes and advance look at Gupta's SQLWindows
- product, a fine example of systems engineering using Microsoft Windows(R)
- to provide a graphical user interface for the building of end-user-programs.
- It will be one of the first commercial systems to exploit the client/server
- application architecture that is fundamental to distributed processing.
-
- In a short period of time, the SQL language has become an important computer
- standard and implementations now exist on a wide variety of mainframe and
- minicomputer systems. SQL is also available on personal computer systems
- in a variety of incarnations. The Database Manager included in the IBM(R)
- Extended Edition of OS/2 may provide the most important implementation of
- SQL, since it is part of Systems Application Architecture (SAA). Products
- competing with the IBM offering, including the Ashton-Tate/Microsoft SQL
- Server, the Oracle Database Add-In for 1-2-3(R), and the Gupta SQL products
- discussed here, use the same SQL statements and process them in the same
- way. This allows software developers to build compatible applications that
- can access data on a variety of servers.
-
- We will present a variety of articles on SQL in coming issues that will
- concentrate on how SQL can serve as a bridge between the application you
- develop and the variety of SQL databases on local and remote systems.
-
- This issue also takes a look at designing OS/2 dynamic-link libraries,
- shareable libraries that run efficiently in a multitasking environment.
- A guide to three OS/2 presentation spaces provides some helpful tips on
- choosing the right one for your application. Four types of OS/2 semaphores
- used for coordinating concurrent threads of execution are explored. We
- examine the new compiler technology in Microsoft QuichBASIC Version 4.0
- and debug windows applications with a utility that lets you manage program
- debugging more effectively.
-
- Look for the DEBUG utility code and all our source code listings on DIAL,
- CompuServe(R), and two public access bullitin boards. On the East Coast,
- users can call (212) 889-6438 to join the RamNet bulletin board. On the
- West Coast, call (415) 284-9151 for the ComOne bulletin board. In either
- case, look for the MSJ directory.──Ed.
-
-
- Masthead
-
- JONATHAN D. LAZARUS
- Editor and Publisher
-
- EDITORIAL
-
- TONY RIZZO
- Technical Editor
-
- KAREN STRAUSS
- Assistant Editor
-
- JOANNE STEINHART
- Production Editor
-
- GERALD CARNEY
- Staff Editor
-
- KIM HOROWITZ
- Editorial Assistant
-
- ART
-
- MICHAEL LONGACRE
- Art Director
-
- VALERIE MYERS
- Associate Art Director
-
- CIRCULATION
-
- WILLIAM B. GRANBERG
- Circulation Manager
-
- L. PERRIN TOMICH
- Assistant to the Publisher
-
- DONNA PUIZINA
- Administrative Assistant
-
- Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
- in part or in whole without permission is prohibited.
-
- Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
- NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
- Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
- President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
- William Neukom, Secretary.
-
- Microsoft Corporation assumes no liability for any damages resulting from
- the use of the information contained herein.
-
- Microsoft, MS-DOS, MS XENIX, and CodeView are registered trademarks of
- Microsoft Corporation. QuickC is a Trademark of Microsoft Corporation.
- CompuServe is a registered trademark of CompuServe Incorporated. dBase
- is a registered trademark of Ashton-Tate Corporation. Hayes is a registered
- trademark of Hayes Microcomputer Products, Inc. IBM and PC/AT are registered
- trademarks of International Business Machines Corporation. Macintosh is a
- registered trademark of Apple Computer, Inc. 1-2-3 is a registered trademark
- of Lotus Development Corporation. PostScript is a registered trademard of
- Adobe Systems, Inc. UNIX is a registered trademark of American Telephone
- and Telegraph Company. WordStar is a registered trademark of MicroPro
- International Corporation.
-
- ████████████████████████████████████████████████████████████████████████████
-
- SQLWindows Brings a Graphical User Interface to SQL Database Applications
-
- ───────────────────────────────────────────────────────────────────────────
- Also see the following related articles:
- SQL Server
- Inside Microsoft Windows
- ───────────────────────────────────────────────────────────────────────────
-
- Craig Stinson
-
- Gupta Technologies, Inc., the Menlo Park, Calif., vendor of distributed
- database software, is preparing to release a one-of-a-kind Microsoft(R)
- Windows application called SQLWindows. The product is an application
- development system specialized for database engines based on structured
- query language (SQL). It is designed to let programmers create data entry
- and manipulation tools that combine the user interface sophistication
- of Windows on the front end with the power and universality of SQL on the
- back end.
-
- Scheduled for shipment in July, SQLWindows is the third link in a triad of
- data management products that Gupta Technologies calls The SQL System.
- The other two components are SQLBase, a database server tailored for the
- 80286/80386 environment, and SQLNet, which is a transparent gateway to
- IBM's DB2 (future versions will offer connectivity to other mainframe DBMS
- engines, as well).
-
- Gupta Technologies was founded in 1984 by Umang P. Gupta and D. Bruce
- Scott, two former key players at Oracle Corp. Gupta was the vice president
- and general manager of the microcomputer products division at Oracle, and
- Scott is coauthor of the Oracle database management system.
-
- The company's first product, SQLBase, made news when it was introduced in
- 1986 because it was the first true database server, as opposed to a
- multiuser DBMS running on a file server, designed specifically to run on
- Microsoft-Networks-based microcomputers. An expanded version of this
- product recently received attention when Lotus Development Corp.
- announced that it would be the engine for the forthcoming Lotus/DBMS
- product.
-
- Lotus will, of course, supply its own application development facilities
- for Lotus/DBMS. However, because the Lotus engine will be essentially the
- same product as SQLBase and will incorporate the same API, applications
- written in any language to run under SQLBase will run unchanged under
- Lotus/DBMS. This includes applications that are written in Gupta's
- SQLWindows.
-
- "SQLWindows works with SQLBase today and with the future Lotus/DBMS
- Server," says Umang Gupta. "And in the future we expect to provide similar
- support for other database servers. A design objective from the very
- beginning has been to make the product work with other SQL engines."
-
- Gupta stresses that the forthcoming SQL Server from Microsoft and Ashton-
- Tate is among the products that are targeted for support.
-
- "SQLWindows is unique," continues Gupta. "There's no other player we know
- of that has a product in the market, or close to market, that is
- Presentation Manager- or Windows-based, engine-independent, and tied both
- to the Windows environment and SQL."
-
- A Presentation Manager version of SQLWindows is also in the works; Gupta
- believes it will be ready by the fourth quarter of 1988. After that,
- the company plans to turn its attention to the Macintosh(R).
-
- "Our long-term plans," says Gupta, "assume that the platforms will be
- PCs, PS/2(R)s running Presentation Manager or Windows, Macintoshes, or
- UNIX(R) workstations running some standardized UNIX windowing system."
-
- Covering all the front-end platforms, providing compatibility with the
- growing number of SQL engines in the marketplace, and at the same time
- expanding SQLNet to provide connectivity with other mainframe DBMS
- engines besides DB2, is, to say the least, an ambitious program. Gupta
- Technologies currently has about 25 employees, including 12 full-time
- programmers. "We plan to increase our development and marketing staff
- significantly in the very near future," says Gupta.
-
-
- Interacting Windows
-
- Gupta and Michael Geary, chief designer of SQLWindows, gave MSJ a look at
- a beta version of the company's newest product. SQLWindows consists of
- three main components: a visual forms-layout editor (similar to the
- dialog editor that comes with the Microsoft Windows Toolkit), an outline
- editor, and a programming language called SQLWindows Applications
- Language (SAL). The outline editor and layout editor function as
- interacting windows: changes made in either window immediately update
- the other window.
-
- Figure 1 shows a sample SQLWindows application in a very early stage of
- development. The programmer has created a sample "About" box. The box is
- displayed in the layout editor window (the lower window in Figure 1),
- while a portion of the corresponding source code is shown as selected in
- the outline window.
-
- The solid diamonds in the outline mark headings that have subordinate
- entries; the open diamonds indicate entries that can't be expanded
- further. In Figure 1, the "Contents" entry, which is subordinate to
- "Dialog Box: AboutBox", has been expanded to reveal two lines of text and
- a single push button.
-
- Figure 2 zooms in on a different portion of the source code for the same
- "About" box. Here the focus is on the push button, and you can see the
- button's title, its location and size attributes, its keyboard
- "accelerator" (the keystroke that will be considered equivalent to a
- mouse click on the button), and the "message actions" associated with
- this push button. In the context of this application there's only one push
- button event of interest (the user clicks on it or performs the equivalent
- action on the keyboard), therefore this object only needs a single
- message action. The message action entry says, in effect, "When the user
- clicks, terminate this dialog and return to the parent window." SAM_Click
- means the button has been clicked, and SalEndDialog and SalParentWindow
- are functions supplied in the SQLWindows Application Language.
-
-
- Progressive Disclosure
-
- Gupta emphasizes that SQLWindows is designed to serve the needs of
- programmers, and the outline approach to source code display is very much
- in keeping with that objective.
-
- "A lot of 4GLs take a sort of dialog box approach to developing code,"
- he says. "You add a field somewhere, and a dialog box pops up and asks you
- all sorts of questions." The drawback to that style of working, asserts
- Gupta, is that you can't see your code as it develops because it's all
- encapsulated in the dialog boxes. "That might suit an end user because
- an end user doesn't necessarily want to see a lot of code. But a
- programmer──at least sometimes──needs to be able to see all of it. You have
- to give him a means of doing that, a sort of progressive disclosure. An
- outline is the appropriate means."
-
- Designer Geary says the idea of incorporating an outline editor into
- SQLWindows occurred to him as he was studying documentation for some
- other application development systems that were not based on an outline.
- "I noticed in a couple of cases that they drew an outline in their
- documentation as a way of explaining what they were doing. And I thought,
- if they draw an outline to explain what they're doing, let's use an
- outline to do what we're doing."
-
- The programmer using SQLWindows can move freely between the outline editor
- and the layout window, working in whichever window is more convenient at
- any given moment. The windows are simply two representations of a common
- underlying data structure; changes made in either window alter the
- underlying structure and are immediately reflected in the other window.
-
- Figures 3 and 4, illustrate these two ways of working. In Figure 3 the
- programmer is inserting a new data field into the layout window, using one
- of the tools in the SQLWindows Draw menu. In Figure 4, the programmer has
- made a change directly to the outline by pressing the Insert key and
- typing.
-
- Note that the programmer's action in Figure 4 is inappropriate. A form
- window cannot be added to a menu, so SQLWindows objects. The
- programmer has three choices: to return to the outline and edit the
- entry (Esc), to throw out the bogus entry (No), and to accept the entry
- (Yes). If Yes is chosen, SQLWindows leaves the entry in place but converts
- it into a comment, thus allowing the programmer to attend to the problem
- later.
-
-
- Access to SQL
-
- All access to SQL takes place via 15 functions supplied in SQLWindows.
- The list includes SQLImmediate, which immediately executes an SQL
- statement; SQLPrepare, which readies an SQL statement for later
- execution; SQLConnect, which connects the application to a specified
- database; SQLDisconnect; SQLFetch, which retrieves a given row of data;
- SQLFetchNext; and SQLFetchPrevious. Figure 5 demonstrates the use of the
- SQLImmediate function.
-
- The application in Figure 5 is a database used by Geary to track bugs in
- the prerelease versions of SQLWindows. Figure 6 shows a completed form
- used as part of this database. The selected section of code is the action
- taken by the New command from the application's Bug menu. A validation
- test is performed first (ValidateInsert). If that fails, an SAL message
- box function is called. Otherwise, two calls to SQLImmediate are made──the
- first to determine the next sequential bug number (select max(bugno) +
- 1), and the second to make the appropriate insertion.
-
- Gupta stresses that the SQLWindows programmer has explicit control of all
- SQL statements generated by the system. "In some other 4GLs," he
- maintains, "SQL statements are implicitly generated, and the programmer or
- user is not actually executing SQL directly. To the extent that that's the
- case, the programmer has less control, or no control. In SQLWindows, you
- can execute SQL statements to your heart's content, any time you want, in
- any order you want."
-
-
- Internal and External Functions
-
- The call to ValidateInsert in Figure 5 is an example of what SQLWindows
- terms "internal functions," functions that are not part of the system's
- built-in repertoire but can be developed by the programmer using SAL
- functions and SQL functions to suit his or her own needs. The system also
- provides for external functions, which can be either dynamic-link
- libraries or static libraries written in C.
-
- Figure 7 shows the declaration of functions stored in two external
- libraries, SQLWIN.EXE and USER.EXE, which is part of the Microsoft Windows
- Toolkit. Figure 8 illustrates the code for an internal function, DoQuery,
- that calls SQL functions to prepare, execute, and fetch one row of data.
-
-
- Why Windows?
-
- Why did Gupta Technologies choose to create an SQL front end in Microsoft
- Windows? Ask the designer and the CEO, and you get two variations of the
- same answer.
-
- Says Geary, "Windows has the user interface that I like. It's really
- oriented toward building applications that are both easy to learn and easy
- to use once you've learned them. It also gives us the tools that we can
- pass on to our SQLWindows developers to let them build applications that
- have a good, crisp feel to them."
-
- Says Gupta, "A major premise of our business is that users want unified
- access to personal, departmental, and corporate database systems and
- that SQL is the lingua franca that makes this access possible. A second
- major premise is that access to database servers will take place from
- advanced applications written to take advantage of the next generation
- of PCs. And that means from OS/2 Presentation Manager and Microsoft
- Windows-and not from dumb terminals."
-
- Clearly, the technology of "industrial-strength database management," as
- Gupta calls it, is changing dramatically these days. SQL is emerging as a
- standard medium for database access. And the arrival of 80386-based PCs
- and high-speed local area networks has enabled database servers running
- on PCs to meet departmental needs as cost effectively as minicomputer-
- based systems.
-
- Gupta Technologies has demonstrated a strong sense of direction with
- these developments. It made an early commitment to SQL and developed
- the first PC-based database server product. In Gupta's words, the company
- "made a few good calls."
-
- On the front end, advances in interface technology have signaled the slow
- beginning of the end for traditional, minicomputer-style, text-based
- 4GLs. Within the next year or two, we can expect to see a myriad of new
- approaches to application development from software houses whose roots are
- planted firmly in the microcomputer world. The OS/2 systems and
- Presentation Manager will play a dominant role in this development. By
- utilizing the Microsoft Windows technology of today, Gupta and
- SQLWindows are in the position to be trendsetters in the Presentation
- Manager and OS/2 environment of the not-so-far-off future.
-
-
- ───────────────────────────────────────────────────────────────────────────
- SQL Server
- ───────────────────────────────────────────────────────────────────────────
-
- SQLWindows is the front-end interface to Gupta's SQLBase. When the Ashton-
- Tate/Microsoft SQLServer, a powerful relational database server for PC
- local area work groups, becomes available, Gupta plans to modify
- SQLWindows to serve as a front-end application for it. SQL Server is based
- on technology licensed from Sybase, and runs on top of OS/2-based networks
- and communicates with workstations running under OS/2 or DOS 3.2 or
- higher.
-
- SQL Server is based on a superset of the ANSI standard Structured Query
- Language (SQL) that applications use to communicate with relational
- databases. SQL is transparent to the user and complements languages such
- as dBASE(R), through which users gain access to data.
-
- SQL Server uses a client/server approach that cleanly splits the functions
- of a database management system into a back-end (server) component where
- data is managed and a front-end (client) component that allows data to be
- accessed by users. SQL is used as the access language between the client
- and server.
-
- The client workstation communicates with SQL Server over PC-based
- networks using TRANSACT-SQL, a superset of ANSI standard SQL with a set of
- extensions that include flow control, temporary variables, transaction
- management, precompiled SQL queries (stored procedures), and special
- stored procedures called triggers.
-
- SQL Server creates the concept of an intelligent database server,
- centralizing database intelligence by using stored procedures and
- triggers. Stored procedures minimize message and network traffic from the
- workstation to the server and can result in performance that is many
- times greater than standard SQL queries. Triggers are stored procedures
- which are automatically executed or "fired" when database values are
- inserted, updated, or deleted. Using stored procedures and triggers, SQL
- Server provides a consistent method of maintaining database integrity
- across applications.
-
- SQL Server has an open platform architecture thus allowing multiple
- workstation applications to access back-end database services. This
- gives users the ability to utilize a variety of PC applications and
- languages against the same data at the same time. A client application
- programming interface (API), DB-Library, may be used by the application
- developer to integrate a program with SQL Server.
-
- SQL Server runs as a single multithreaded process under OS/2 with the
- capability of managing many users and multiple databases
- simultaneously. This results in high transaction throughput and
- efficient use of computer resources. A 386-based SQL Server can be
- expected to support more than 35 users accessing a 300Mb database with
- response time under a second.
-
- With its transaction-oriented DBMS kernel, SQL Server makes the database
- constantly available for such administrative tasks as backup, recovery,
- database updates, design changes, and integrity rule changes even while
- users continue to access the database. SQL Server also maximizes
- transaction throughput so that performance, as seen by the individual
- user, stays virtually constant as users are added to the network. The
- system can be administered from anywhere on the network, so that there is
- no need for a separate administrator's console.
-
- ───────────────────────────────────────────────────────────────────────────
- Inside Microsoft Windows
- ───────────────────────────────────────────────────────────────────────────
-
- Michael Geary, designer of SQLWindows, likes a lot of things about
- programming in Microsoft Windows.
-
- "To begin with," he says, "there's the user interface. One of the nice
- things about Windows is that the Windows style guide spells out an excellent
- user interface in great detail. That makes life easier for the programmer,
- and it also makes things much easier for the user. Once someone has learned
- one application, it's a lot easier to go learn another one."
-
- Geary said he appreciates the fact that Windows provides the tools to build
- its interface. "When I want to write a menu, I don't have to write code to
- say how menus work. All the basic user interface tidbits are built in," he
- said.
-
-
- Messages and Child Windows
-
- Geary, who had programmed extensively in both the Macintosh and the MS-
- DOS(R) environments before taking on SQLWindows (among his credits is the
- respected communications package Transend/PC), was particularly impressed
- by two aspects of Windows programming: messages and child windows.
-
- "I'm often asked to compare the Macintosh and Windows," he says. "From the
- user's point of view and the programmer's point of view, they're very
- similar. But I was really struck with the way Windows took the Mac's
- notion of events and generalized that into the message concept. Messages
- are like a superset of events. What you can do in Windows that you can't
- do on the Mac is make up your own messages and send them from one
- application to another. That's what makes DDE [Dynamic Data Exchange]
- possible; DDE is just a protocol for sending messages."
-
- As for child windows, there's basically only one kind of window on the
- Macintosh, Geary explains. Each window is essentially a peer of all the
- others, and once a program has built a window it's up to the programmer to
- manage everything that goes on inside it. The Macintosh offers such tools as
- push buttons, radio buttons, scroll bars, and so on, but the program has to
- manage the details of where they're placed and how they're used.
-
- In Windows, the common objects by means of which the user interacts with the
- application, such as the various components of a dialog box, are
- typically implemented as child windows. And because child windows behave
- essentially the same way parent windows do, a lot of things happen
- automatically that would have required explicit programming on the
- Macintosh.
-
-
- Subclassing
-
- "Another thing I like about the child window concept," adds Geary, "is that
- Windows supplies you with a number of predefined classes of child windows
- and then makes it easy for you to subclass them. You just take the
- predefined class and add on some behavior of your own by interposing your
- own window function in front of the one that comes with it, in effect
- filtering out the messages that you want to handle differently."
-
- Geary used this technique extensively in programming SQLWindows. Objects in
- the forms-layout editor window have to behave one way when the user of
- SQLWindows is developing an application and another way when the end user of
- that application is working with it. For example, the developer needs to be
- able to click on such objects as push buttons in order to size and position
- them; the end user wants them to behave like normal push buttons.
-
- "I was able to do all that split behavior just by using the subclassing
- technique," Geary explains. "I put my own window function ahead of the
- standard one. At design time I handle many of the messages differently; at
- run time, I just pass them through."
-
- Geary says he created a number of window classes from scratch, including a
- list window that handles columns as well as rows, somewhat like a
- spreadsheet, and can fetch data from a database on the fly (a "regular"
- Windows list window requires that all its data be in memory).
-
- "The nice thing about this," he elaborated, "is that my own window classes
- coexist with the built-in Windows classes, and there's not much special I
- have to do to make my own objects work side by side with pre-existing ones.
- The rest of my program treats them all the same; it doesn't care whether
- the window was provided by me or by Windows."
-
-
- Resource Management
-
- Other advantages to programming in Windows involve the management of
- memory and the keyboard. Because all code in Windows is relocatable and
- discardable, the programmer, according to Geary, is liberated from both
- memory fragmentation problems and the need to juggle the trade-offs
- associated with code overlays.
-
- "In programs that I've done in the past," he explains, "I've had to use
- overlays a lot. And it's always been a pain trying to decide, ╘How heavily
- do I overlay this? What's the target machine size? Whom do I optimize this
- for?' I've been through that process a number of times and it's awful. You
- can't satisfy everybody.
-
- "But in Windows, all you do is break up the program into a lot of code
- segments. Each code segment is an independent entity as far as being loaded
- into memory is concerned, and Windows just loads in as many as it can fit.
- When it runs out of room, it moves them around or discards them as needed.
- In other words, it optimizes for all the different machines at once.
-
- "You have to do a little extra work to take advantage of Windows' memory
- management, but it's much better than dealing with a fixed-overlay
- structure."
-
- As for keyboard management, Geary applauds the fact that Windows supplies
- the programmer with information that eliminates the need for direct
- programming of the hardware.
-
- "In Windows," he said, "keyboard handling is very simple. They send you
- messages for everything. With any keypress, you get WM_KEYDOWN. When any
- key is released, you get WM_KEYUP. You can distinguish between the first
- press of a key and presses that come from the auto repeat mechanism.
-
- "Basically, you can look at the keyboard at whatever level you like, without
- being an ill-behaved program. There's no incentive to go to the hardware,
- because they've provided all the stuff you want to do in the first place."
-
- Finally, Geary cites the advantages afforded to programmers and users
- alike by DDE and the Windows Clipboard. He believes the presence of a well-
- defined standard for interapplication communications will have a long-
- range impact on the types of software people write.
-
- "Philosophically, the Clipboard and DDE allow people to get away from
- building complex all-in-one applications and open up the possibility of
- getting back to building smaller programs that work effectively together."
-
- ████████████████████████████████████████████████████████████████████████████
-
- The Graphics Programming Interface: A Guide to OS/2 Presentation Spaces
-
- Charles Petzold
-
- Programmers experienced with Microsoft(R) Windows will discover that much of
- the OS/2 systems Presentation Manager is familiar territory. The windowing
- and user interface portion of the Presentation Manager is obviously derived
- from Windows, and many of the window messages are identical.
-
- However, the Graphics Programming Interface (GPI) is mostly new and
- offers some very significant enhancements over the Windows Graphics Device
- Interface (GDI). GPI is derived largely from IBM's Graphical Data Display
- Manager (GDDM) and 3270 Graphics Control Program (GCP) with some
- features, such as bitmaps and regions, derived from Windows. GPI also
- reflects influence from many other graphics interfaces, ranging from the
- Graphical Kernel System (GKS) to PostScript(R).
-
- One of the first hurdles in approaching GPI is dealing with the concept of
- the presentation space (PS). What makes it difficult is that the
- Presentation Manager supports three different types of presentation
- spaces, the "cached micro-PS," the "micro-PS," and the "normal-PS." Which
- type of presentation space you choose for your application depends on a
- variety of factors, some of which I'll discuss here.
-
- The subject becomes more interesting when you realize that the type of
- presentation space you choose determines to some degree how you structure
- a Presentation Manager application for drawing. I'll demonstrate this
- in three programs, CACHEDPS, MICROPS, and NORMALPS, each of which uses one
- of the three available types of presentation space.
-
- Each program displays the same simple graphics shown in Figure 1. The text
- string "Graphics Programming Interface" is displayed in the center of the
- window with dotted lines drawn from the corners of the text string to the
- corners of the window. Both the text and the dotted lines are displayed in
- red.
-
-
- GPI Drawing Functions
-
- Before we discuss the presentation space, let's take a quick look at
- the functions that these three programs use for drawing the text string
- and the dotted lines. The CACHEDPS.C program calls these GPI drawing
- functions in its ClientWndProc function during processing of the WM_PAINT
- message.
-
- Many of the GPI drawing functions require a point in x and y coordinates,
- which is provided to the function as a structure of type POINTL. The
- POINTL structure is defined in the OS2DEF.H header file like this:
-
- typedef struct _POINTL
- {
- LONG x ;
- LONG y ;
- }
- POINTL ;
-
- The LONG data type is simply a 32-bit unsigned long integer.
-
- To define a POINTL structure in a C program you can use
-
- struct _POINTL ptl ;
-
- or more simply:
-
- POINTL ptl ;
-
- By convention, the names of POINTL variables begin with the prefix ptl.
-
- (One big difference between GPI and some other high-level graphics
- languages is that GPI does not use floating point. This is for performance
- reasons──floating point calculations on personal computers are not yet fast
- enough for a highly interactive graphical system such as the Presentation
- Manager.)
-
- GPI includes the concept of a current position. To draw a line, the first
- step is to set the current position to the beginning of the line. This
- requires assigning coordinate values to the x and y fields of the POINTL
- structure and calling the GpiMove function:
-
- ptl.x = ... ;
- ptl.y = ... ;
- GpiMove (hps,&ptl) ;
-
- I'll discuss the hps parameter of GpiMove shortly. GpiMove does not draw
- anything; it merely sets the current position. To draw the line, set the
- fields of the structure to the end point of the line and call GpiLine:
-
- ptl.x = ... ;
- ptl.y = ... ;
- GpiLine (hps,&ptl) ;
-
- The GpiLine function also moves the current position to the indicated
- point.
-
- It certainly is a nuisance to set two fields of a POINTL structure before
- calling GpiMove or GpiLine. However, you'll find that the use of a
- structure to represent points offers a nice clean consistency in GPI
- function syntax. For example, the GpiQueryCurrentPosition function, which
- obtains the current position, has the same syntax as GpiMove:
-
- GpiQueryCurrentPosition (hps,&ptl) ;
-
- Upon returning from the function, the ptl structure is filled with the
- coordinates of the current position.
-
- The use of a structure for points also allows syntax consistency in those
- GPI functions that require multiple points. One such function is
- GpiPolyLine, which draws a series of lines beginning at the current
- position. You define an array of POINTL structures like this:
-
- POINTL aptl [15] ;
-
- The call to GpiPolyLine requires the number of points and the array name:
-
- GpiPolyLine (hps,15L, aptl) ;
-
- That GpiPolyLine call is almost equivalent to this code:
-
- for (i = 0 ; i < 15 ; i++)
- GpiLine (hps,aptl + i) ;
-
- The points you specify in GPI functions are in "world coordinates."
- Although GPI supports several transforms to convert world coordinates to
- pixels, I'll defer discussion of these transforms. By default, world
- coordinates are in units of pixels relative to the lower left corner of
- the window. Values on the x (horizontal) axis increase to the right;
- values on the y (vertical) axis increase going up.
-
- CACHEDPS.C uses the function GpiCharStringAt to display the text string:
- GpiCharStringAt (hps, &ptl,lTextLength,cText) ;
-
- Under default conditions, the baseline of the left side of the first
- character is positioned at the point given in the ptl structure. The cText
- parameter is a pointer to a character string or a character array, and
- lTextLength is the length of the string. For example, this code displays
- the text "Hello" at the point (10, 10):
-
- ptl.x = 10 ;
- ptl.y = 10 ;
- GpiCharStringAt (hps, &ptl,5L,"Hello") ;
-
- By default, the font that GPI uses is the normal system font, the same
- font that appears in Presentation Manager title bars, menus, and dialog
- boxes.
-
-
- GPI Attributes
-
- CACHEDPS.C calls two functions to set the attributes that GPI uses when
- drawing. Before drawing anything, the program calls GpiSetColor:
-
- GpiSetColor (hps,CLR_RED) ;
-
- This causes all text and lines displayed after this function call to be
- colored red. Before displaying the four lines, CACHEDPS.C calls another
- attribute function to set a dotted line type style:
-
- GpiSetLineType (hps, LINETYPE_DOT) ;
-
- GPI also has functions that obtain information from the system. One of
- the query functions used in CACHEDPS.C is GpiQueryTextBox. This function
- fills a POINTL array with the coordinate positions of the four corners of
- an imaginary box that would surround a particular character string if
- the string were displayed at the point (0, 0). The CACHEDPS program uses
- this information to draw the four lines.
-
- Near the beginning of WM_PAINT processing, CACHEDPS.C makes a call to
- GpiCreateLogColorTable. In the version of the Presentation Manager I used
- for this article, the default background color is black and the default
- foreground color is white. Thus, the GpiErase function erases the window
- to black. The call to GpiCreateLogColorTable in CACHEDPS reverses this so
- that the default background color is white and the foreground color is
- black. Whether this function will be necessary in the final version of the
- Presentation Manager is not clear as of this writing.
-
- The calls to GpiRestorePS, GpiSavePS, and GpiResetPS in CACHEDPS.C during
- the WM_PAINT message are temporary fixes to avoid some bugs in the
- version of the Presentation Manager I have. These functions should not
- be necessary when the Presentation Manager is in final shape.
-
-
- The PS and DC
-
- The first parameter to every GPI function in CACHEDPS.C is the variable
- named hps. This variable is defined as type HPS, a handle to a
- presentation space.
-
- Before a Presentation Manager program can draw on the display, it must
- either create a presentation space or use a presentation space that has
- already been created by the Presentation Manager. How you do this depends
- on the type of presentation space you decide to use.
-
- The presentation space is basically a data structure internal to the
- Presentation Manager. All the attributes of the presentation space──the
- current position, the current background and foreground colors, the
- current line style type, transforms, and others──are stored here. For the
- normal-PS, the presentation space can also store collections of GPI
- function calls.
-
- A Windows programmer might at first assume that the presentation space is
- equivalent to the Windows display context (DC), but they are really two
- different things. In fact, the Presentation Manager includes the
- concept of a display context.
-
- In the Presentation Manager, the display context usually describes a
- physical output device, such as the video display, printer, or plotter. A
- display context can also be a block of memory that is used as if it were
- an output device; it can also be a metafile. You can think of the display
- context as comprising the output device and its device driver.
-
- A presentation space does not itself imply any type of output device.
- Instead, the presentation space must be associated with a particular
- display context. The reason for the separation of the presentation space
- and the display context will be evident later. The conceptual
- relationship between an application program, the presentation space, and
- the display context is shown in Figure 2.
-
- Of the three different types of presentation spaces supported by the
- Presentation Manager, normal-PS, sometimes also called the full-PS, is the
- most versatile, with micro-PS second and cached micro-PS being the least
- versatile.
-
- To use a normal-PS or micro-PS, a program must create the presentation
- space by calling the function GpiCreatePS. Strictly speaking, these are
- the only two types of presentation space supported by GPI. The cached
- micro-PS is provided courtesy of the component of the Presentation
- Manager concerned with the windowing and user interface functions,
- sometimes called the "Win" or "User" component. The Presentation Manager
- creates the micro-PS for you.
-
-
- The Cached Micro-PS
-
- The CACHEDPS program shown in Figure 3 uses a cached micro-PS. (For more
- information on cached micro-PS see Microsoft(R) Operating System/2 Software
- Development Kit, Presentation Manager Specification, Vol. 1, p. 210) A
- program can obtain a handle to a cached micro-PS by calling WinBeginPaint
- or WinGetPS. CACHEDPS.C uses both of these functions.
-
- You can call the WinGetPS function while processing any window message.
- After you're done with the presentation space you release it by calling
- WinReleasePS:
-
- hps = WinGetPS (hwnd) ;
- ∙
- ∙
- ∙
- < use GPI functions >
- ∙
- ∙
- ∙
- WinReleasePS (hps) ;
-
- CACHEDPS calls WinGetPS during the WM_CREATE message. The only GPI
- function the program calls at that time is GpiQueryTextBox. CACHEDPS needs
- to obtain the dimensions of the character box only once because the height
- and width of the text string will not change while the program is
- running.
-
- The functions WinGetPS and WinReleasePS must be called as a pair while
- processing a single message. Do not call WinGetPS while processing one
- message and WinReleasePS while processing another.
-
- During processing of a WM_PAINT message, a program calls WinBeginPaint to
- obtain a handle to a cached micro-PS and WinEndPaint to release the
- handle:
-
- hps = WinBeginPaint (hwnd, NULL,NULL) ;
- ∙
- ∙
- ∙
- < use GPI functions >
- ∙
- ∙
- ∙
- WinEndPaint (hps) ;
-
- A cached micro-PS has several important characteristics. First, the cached
- micro-PS is always associated with the video display, or more precisely,
- with a window on the video display. That makes the cached micro-PS easy to
- use (WinGetPS requires only one parameter) but also limits it to the screen.
-
- Second, all attributes of the presentation space are set to their default
- values when the handle is obtained by calling WinGetPS or WinBeginPaint.
- This is why CACHEDPS.C sets the color to red and the line style type to
- dotted when it processes the WM_PAINT message.
-
- Finally, when using the cached micro-PS a program usually draws in units
- of pixels. You can change that with a few calls to certain GPI functions,
- but it is not quite as easy as when a program creates its own
- presentation space by calling GpiCreatePS.
-
- Windows programmers will note that CACHEDPS is structured much like a
- Windows program. If you're converting a program from Windows to the
- Presentation Manager, you may want to use a cached micro-PS to ease the
- conversion.
-
-
- The WM_PAINT Message
-
- CACHEDPS does all of its drawing while processing the WM_PAINT message.
- Handling the WM_PAINT message correctly is an important structural
- consideration in both Windows programs and Presentation Manager
- programs.
-
- A window procedure receives a WM_PAINT message whenever an area of the
- window has become invalid, meaning that the area no longer contains what
- the program originally drew there. If a portion of a window has been
- covered and then becomes uncovered, the Presentation Manager will post a
- WM_PAINT message to that window. A window procedure can also receive a
- WM_PAINT message when the window has been changed in size.
-
- Although a Presentation Manager program can draw on a window at almost any
- time, the program must be able to update the entire window on receipt of a
- WM_PAINT message. Programmers often satisfy this requirement by doing all
- window painting during the WM_PAINT message and none at any other time.
- As you'll see, dealing with WM_PAINT messages becomes somewhat simpler
- with the normal-PS.
-
-
- The Micro-PS
-
- Because the cached micro-PS is always associated with a window on the
- video display, you cannot use a cached micro-PS for drawing on another
- output device, such as a printer or a plotter. To use a printer or
- plotter you must create either a micro-PS or normal-PS. A program can
- also create a micro-PS for drawing on the program's window. The MICROPS
- program in Figure 4 shows how this is done.
-
- MICROPS.C creates the presentation space by calling GpiCreatePS while
- processing the WM_CREATE message in ClientWndProc. GPIT_MICRO in the
- GpiCreatePS function tells GPI to create a micro-PS. GpiCreatePS also
- requires a handle to a display context with which the presentation space
- will be associated. For a video display window, this display context
- handle is obtained from WinOpenWindowDC:
-
- hdc = WinOpenWindowDC (hwnd) ;
-
- The presentation space includes an imaginary drawing surface called the
- "presentation page." (I promise that this is the last term I'll be
- presenting here.) You define the size and units of the presentation page
- in the GpiCreatePS function. You set the size of the page by the SIZEL
- structure. Setting the cx and cy fields to 0 makes the page size the same
- as the size of the entire video display. The PU_PELS identifier indicates
- that the coordinates of the page are in units of pixels. You can instead
- use one of the identifiers shown in Figure 5 for units other than pixels.
-
- Using a pixel-coordinate page is often the best approach for simple
- graphics output. Information obtained from the Presentation Manager
- windowing system, such as the size of the window obtained during the
- WM_SIZE message, is always in units of pixels regardless of the
- presentation page size. You would have to convert those coordinates and
- sizes to the page units using GpiConvert.
-
- The presentation space handle obtained from GpiCreatePS is valid until the
- presentation space is destroyed by a call to GpiDestroyPS. MICROPS
- destroys the presentation space while it's processing the WM_DESTROY
- message, which is the last message the window procedure receives. Until
- that time, the hps value must be used during all WM_PAINT and WM_SIZE
- messages, so it is stored in a static variable.
-
- Note that MICROPS sets the color and line style type during WM_CREATE
- processing rather than during WM_PAINT processing. The micro-PS exists
- until it is explicitly destroyed so any GPI attributes that a program sets
- remain in the presentation space until they are changed. In contrast, a
- cached micro-PS is reset to default attributes each time a program
- obtains it.
-
- The syntax of the function WinBeginPaint for a micro-PS is a little
- different from that for a cached micro-PS. Rather than use WinBeginPaint
- to obtain the presentation space handle, the existing handle is passed to
- the function as the second parameter. (In the version of the Presentation
- Manager that I used for this article, WinBeginPaint and WinEndPaint were not
- working correctly; that's why they are shown in MICROPS.C between comment
- delimiters.)
-
- Windows programmers, watch out! You'll find it natural to do something
- like this during the WM_PAINT message when using a cached micro-PS:
-
- hps = WinBeginPaint (hwnd, NULL,NULL) ;
- ∙
- ∙
- ∙
- < draw using default colors >
- ∙
- ∙
- ∙
- GpiSetColor (hps,CLR_RED) ;
- ∙
- ∙
- ∙
- < draw using red >
- ∙
- ∙
- ∙
- WinEndPaint (hps) ;
-
- However, if you switch to a micro-PS, you might first alter the code like
- this:
-
- WinBeginPaint (hwnd,hps, NULL) ;
- ∙
- ∙
- ∙
- < draw using default colors >
- ∙
- ∙
- ∙
- GpiSetColor (hps,CLR_RED) ;
- ∙
- ∙
- ∙
- < draw using red >
- ∙
- ∙
- ∙
- WinEndPaint (hps) ;
-
- This will work fine during the first WM_PAINT message but not during
- subsequent messages because the color will still be set to red. Remember
- that the micro-PS retains all GPI attributes that you change until you
- change them again. You can avoid such problems by using GpiSavePS and
- GpiRestorePS to save and restore the GPI state:
-
- WinBeginPaint (hwnd,hps, NULL) ;
- ∙
- ∙
- ∙
- < draw using default
- colors >
- ∙
- ∙
- ∙
- GpiSavePS (hps) ;
- GpiSetColor (hps,CLR_RED) ;
- ∙
- ∙
- ∙
- < draw using red >
- ∙
- ∙
- ∙
- GpiRestorePS (hps,-1L) ;
- WinEndPaint (hps) ;
-
- Or you can explicitly set the color back to the default value at the end
- of WM_PAINT processing:
-
- WinBeginPaint (hwnd, hps, NULL) ;
- ∙
- ∙
- ∙
- < draw using default colors >
- ∙
- ∙
- ∙
- GpiSetColor (hps,CLR_RED) ;
- ∙
- ∙
- ∙
- < draw using red >
- ∙
- ∙
- ∙
- GpiSetColor (hps, CLR_DEFAULT) ;
- WinEndPaint (hps) ;
-
- Comparing CACHEDPS.C and MICROPS.C listings reveals only a subtle change
- in structure-two GPI attribute functions called during the WM_PAINT message
- in CACHEDPS are handled in MICROPS during the WM_CREATE message. The real
- change in structure occurs when you move up to the normal-PS.
-
-
- The Normal-PS
-
- The micro-PS gives a program access to only a subset of the GPI
- functions. Although it is a very substantial subset and includes all the
- basics, it excludes all functions that pertain to GPI "segments"; these
- are supported only by the normal-PS.
-
- A segment (not to be confused with a memory segment in 80286
- microprocessor architecture) is a stored collection of graphics drawing
- and attribute commands. You open a segment, you call a bunch of GPI
- functions, and then you close the segment. All the functions are stored
- in the segment. You can then cause these commands to be drawn on the
- output device by issuing one of several functions such as GpiDrawChain.
- The NORMALPS program shown in Figure 6 gives you a little taste of
- segments.
-
- The graphics output of NORMALPS depends only on the size of the window.
- So, during the WM_SIZE message, a segment is created by a call to
- GpiOpenSegment. The attribute and drawing commands are called just as they
- were in the two earlier programs. But these graphics orders are not
- displayed just yet; they are instead saved in the segment. During the
- WM_PAINT message, the segment is displayed by calling GpiDrawChain.
- Obviously this simplifies WM_PAINT processing considerably.
-
- You can create many segments in your program; they are saved as part of
- the presentation space. A segment can be either chained or unchained.
- The root chain consists of all chained segments; GpiDrawChain draws all
- the segments in the root chain. Unchained segments can be called from
- other segments. When an unchained segment is called, the coordinate
- positions specified in all the drawing commands in the segment can be
- transformed with translation, scaling, or rotation, which is useful for
- graphical modeling.
-
- You can edit segments, change the order in which segments in the root
- chain are drawn, and test whether an object drawn in a segment is within a
- specified radius of a particular point. This is very handy for mouse hit-
- testing.
-
- Besides the support of segments, the normal-PS has another advantage over
- the micro-PS. It is the only presentation space that can be
- reassociated with another display context; that is, you can disassociate
- the presentation space from the video display and then associate it with
- the printer. Because the segments are part of the presentation space,
- you need not recreate them. The segments are ready to be drawn on the
- printer.
-
- Of course, the use of segments is overkill in NORMALPS, as it would be in
- many other small Presentation Manager programs. The basic rule is to use
- the simplest type of presentation space that meets your needs. If you
- can do everything you need to do using the cached micro-PS, use that.
- Don't take a full set of luggage on an overnight trip.
-
-
- Figure 2: The relatonship between an application program, the presentation
- space, and the display context.
-
- ┌─────────────────────────────────────────────────────────────┐
- │ │█
- │ ╔══════════════════════════════════════════╗ │█
- │ ║ Application Program ║ │█
- │ ╚════════════════════╤═════════════════════╝ │█
- │ draws to the │█
- │ │█
- │ ╔══════════════════════════════════════════╗ │█
- │ ║ Presentation Space ║ │█
- │ ╚════════════════════╤═════════════════════╝ │█
- │ which is associated with a │█
- │ │█
- │ ╔══════════════════════════════════════════╗ │█
- │ ║ Display Context ║ │█
- │ ╚════════════════════╤═════════════════════╝ │█
- │ which causes output to be displayed on a │█
- │ │█
- │ ╔══════════════════════════════════════════╗ │█
- │ ║ Physical Output Device ║ │█
- │ ╚══════════════════════════════════════════╝ │█
- └─────────────────────────────────────────────────────────────┘█
- ██████████████████████████████████████████████████████████████
-
-
- Figure 3:
-
- CACHEDPS Make File
-
- cachedps.obj : cachedps.c
- cl -c -G2sw -W2 -Zp cachedps.c
-
- cachedps.exe : cachedps.obj cachedps.def
- link cachedps, /align:16, NUL, os2, cachedps
-
- CACHEDPS.DEF Module Definition File
-
- NAME CACHEDPS
- DESCRIPTION 'Demonstrates Cached Micro-PS(C) Charles Petzold, 1988'
- PROTMODE
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- CACHEDPS.C-Demonstrates Cached Micro-PS
-
- #define INCL_GPI
-
- #include <os2.h>
- #include <stddef.h>
-
- MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
-
- main ()
- {
- static CHAR szClientClass [] = "CachedPS" ;
- HAB hab ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
- WinRegisterClass (hab, szClientClass, ClientWndProc,
- CS_SIZEREDRAW, 0) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX,
- szClientClass, "Cached Micro-PS",
- 0L, NULL, 0, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
- HWND hwnd ;
- USHORT msg ;
- MPARAM mp1 ;
- MPARAM mp2 ;
- {
- static CHAR szText [] = "Graphics Programming Interface" ;
- static LONG lTextLength = sizeof szText - 1L ;
- static LONG alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
- CLR_NEUTRAL, RGB_BLACK } ;
- static POINTL ptlTextStart, aptlLineStart [4],
- aptlTextBox [TXTBOX_COUNT] ;
- static SHORT cxClient, cyClient ;
- HPS hps ;
- POINTL ptl ;
- SHORT sIndex ;
-
- switch (msg)
- {
- case WM_CREATE:
- hps = WinGetPS (hwnd) ;
-
- GpiQueryTextBox (hps, lTextLength, szText,
- TXTBOX_COUNT, aptlTextBox) ;
-
- WinReleasePS (hps) ;
- break ;
-
- case WM_SIZE:
- cxClient = LOUSHORT (mp2) ;
- cyClient = HIUSHORT (mp2) ;
-
- ptlTextStart.x = (cxClient -
- aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
- aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
-
- ptlTextStart.y = (cyClient -
- aptlTextBox [TXTBOX_TOPLEFT].y -
- aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
-
- for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
- {
- aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
- aptlLineStart [sIndex].x += ptlTextStart.x ;
- aptlLineStart [sIndex].y += ptlTextStart.y ;
- }
- break ;
-
- case WM_PAINT:
- hps = WinBeginPaint (hwnd, NULL, NULL) ;
-
- GpiSavePS (hps) ; /* temp fix */
- GpiResetPS (hps, GRES_ATTRS) ; /* temp fix */
- GpiCreateLogColorTable (hps, LCOL_RESET,
- LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
-
- GpiErase (hps) ;
-
- GpiSetColor (hps, CLR_RED) ;
-
- GpiCharStringAt (hps, &ptlTextStart,
- lTextLength, szText) ;
-
- GpiSetLineType (hps, LINETYPE_DOT) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMLEFT) ;
- ptl.x = 0 ;
- ptl.y = 0 ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMRIGHT) ;
- ptl.x = cxClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
- ptl.y = cyClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
- ptl.x = 0 ;
- GpiLine (hps, &ptl) ;
-
- GpiRestorePS (hps, -1L) ; /* temp fix */
-
- WinEndPaint (hps) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return FALSE ;
- }
-
-
- Figure 4:
-
- MICROPS Make File
-
- microps.obj : microps.c
- cl -c -G2sw -W2 -Zp microps.c
-
- microps.exe : microps.obj microps.def
- link microps, /align:16, NUL, os2, microps
-
- MICROPS.DEF Module Definition File
-
- NAME MICROPS
- DESCRIPTION 'Demonstrates Micro-PS(C) Charles Petzold, 1988'
- PROTMODE
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- MICROPS.C-Demonstrates Micro-PS
-
- #define INCL_WIN
- #define INCL_GPI
-
- #include <os2.h>
- #include <stddef.h>
-
- MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
-
- HAB hab ;
-
- main ()
- {
- static CHAR szClientClass [] = "MicroPS" ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
- WinRegisterClass (hab, szClientClass, ClientWndProc,
- CS_SIZEREDRAW, 0) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX,
- szClientClass, "Micro-PS",
- 0L, NULL, 0, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
- HWND hwnd ;
- USHORT msg ;
- MPARAM mp1 ;
- MPARAM mp2 ;
- {
- static CHAR szText [] = "Graphics Programming Interface" ;
- static HPS hps ;
- static LONG lTextLength = sizeof szText - 1L ;
- static LONG alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
- CLR_NEUTRAL, RGB_BLACK } ;
- static POINTL ptlTextStart, aptlLineStart [4],
- aptlTextBox [TXTBOX_COUNT] ;
- static SHORT cxClient, cyClient ;
- HDC hdc ;
- POINTL ptl ;
- SHORT sIndex ;
- SIZEL sizl ;
-
- switch (msg)
- {
- case WM_CREATE:
- hdc = WinOpenWindowDC (hwnd) ;
-
- sizl.cx = 0 ;
- sizl.cy = 0 ;
-
- hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS |
- GPIF_DEFAULT | GPIT_MICRO |
- GPIM_NORMAL | GPIA_ASSOC) ;
-
- GpiCreateLogColorTable (hps, LCOL_RESET,
- LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
-
- GpiQueryTextBox (hps, lTextLength, szText,
- TXTBOX_COUNT, aptlTextBox) ;
-
- GpiSetColor (hps, CLR_RED) ;
- GpiSetLineType (hps, LINETYPE_DOT) ;
- break ;
-
- case WM_SIZE:
- cxClient = LOUSHORT (mp2) ;
- cyClient = HIUSHORT (mp2) ;
-
- ptlTextStart.x = (cxClient -
- aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
- aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
-
- ptlTextStart.y = (cyClient -
- aptlTextBox [TXTBOX_TOPLEFT].y -
- aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
-
- for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
- {
- aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
- aptlLineStart [sIndex].x += ptlTextStart.x ;
- aptlLineStart [sIndex].y += ptlTextStart.y ;
- }
-
- break ;
-
- case WM_PAINT:
- /* BeginPaint (hwnd, hps, NULL) ; */
-
- GpiErase (hps) ;
-
- GpiCharStringAt (hps, &ptlTextStart,
- lTextLength, szText) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMLEFT) ;
- ptl.x = 0 ;
- ptl.y = 0 ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMRIGHT) ;
- ptl.x = cxClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
- ptl.y = cyClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
- ptl.x = 0 ;
- GpiLine (hps, &ptl) ;
-
- /* EndPaint (hps) ; */
-
- WinValidateRect (hwnd, NULL, FALSE) ; /* temp fix */
- break ;
-
- case WM_DESTROY:
- GpiDestroyPS (hps) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return FALSE ;
- }
-
-
- Figure 5: The identifiers used in the GpiCreatePS function to set the units
- of the presentation page.
-
- Page Units
- Identifier Units
-
- PU_ARBITRARY Pixels (with adjustment)
- PU_PELS Pixels
- PU_LOMETRIC 0.1 millimeter
- PU_HIMETRIC 0.01 millimeter
- PU_LOENGLISH 0.01 inch
- PU_HIENGLISH 0.001 inch
- PU_TWIPS 1/1440 inch
-
-
- Figure 6:
-
- NORMALPS Make File
-
- normalps.obj : normalps.c
- cl -c -G2sw -W2 -Zp normalps.c
-
- normalps.exe : normalps.obj normalps.def
- link normalps, /align:16, NUL, os2, normalps
-
- NORMALPS.DEF Module Definition File
-
- NAME NORMALPS
- DESCRIPTION 'Demonstrates Normal-PS(C) Charles Petzold, 1988'
- PROTMODE
- HEAPSIZE 1024
- STACKSIZE 8192
- EXPORTS ClientWndProc
-
- NORMALPS.C-Demonstrates Normal-PS
-
- #define INCL_WIN
- #define INCL_GPI
-
- #include <os2.h>
- #include <stddef.h>
-
- MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
-
- HAB hab ;
-
- main ()
- {
- static CHAR szClientClass [] = "NormalPS" ;
- HMQ hmq ;
- HWND hwndFrame, hwndClient ;
- QMSG qmsg ;
-
- hab = WinInitialize (0) ;
- hmq = WinCreateMsgQueue (hab, 0) ;
- WinRegisterClass (hab, szClientClass, ClientWndProc,
- CS_SIZEREDRAW, 0) ;
-
- hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
- WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
- | FS_SYSMENU | FS_MINMAX,
- szClientClass, "Normal-PS",
- 0L, NULL, 0, &hwndClient) ;
-
- while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
- WinDispatchMsg (hab, &qmsg) ;
-
- WinDestroyWindow (hwndFrame) ;
- WinDestroyMsgQueue (hmq) ;
- WinTerminate (hab) ;
-
- return 0 ;
- }
-
- MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
- HWND hwnd ;
- USHORT msg ;
- MPARAM mp1 ;
- MPARAM mp2 ;
- {
- static CHAR szText [] = "Graphics Programming Interface" ;
- static HPS hps ;
- static LONG lSegmentName = 1 ;
- static LONG lTextLength = sizeof szText - 1L ;
- static LONG alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
- CLR_NEUTRAL, RGB_BLACK } ;
- static POINTL aptlTextBox [TXTBOX_COUNT] ;
- HDC hdc ;
- POINTL ptl, ptlTextStart, aptlLineStart [4] ;
- SHORT cxClient, cyClient, sIndex ;
- SIZEL sizl ;
-
- switch (msg)
- {
- case WM_CREATE:
- hdc = WinOpenWindowDC (hwnd) ;
-
- sizl.cx = 0 ;
- sizl.cy = 0 ;
-
- hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS |
- GPIF_DEFAULT | GPIT_NORMAL |
- GPIM_NORMAL | GPIA_ASSOC) ;
-
- GpiCreateLogColorTable (hps, LCOL_RESET,
- LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
-
- GpiQueryTextBox (hps, lTextLength, szText,
- TXTBOX_COUNT, aptlTextBox) ;
-
- GpiSetDrawControl (hps, DCTL_ERASE, DCTL_ON) ;
- GpiSetDrawingMode (hps, DM_RETAIN) ;
- break ;
-
- case WM_SIZE:
- cxClient = LOUSHORT (mp2) ;
- cyClient = HIUSHORT (mp2) ;
-
- ptlTextStart.x = (cxClient -
- aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
- aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
-
- ptlTextStart.y = (cyClient -
- aptlTextBox [TXTBOX_TOPLEFT].y -
- aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
-
- for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
- {
- aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
- aptlLineStart [sIndex].x += ptlTextStart.x ;
- aptlLineStart [sIndex].y += ptlTextStart.y ;
- }
-
- GpiDeleteSegment (hps, lSegmentName) ;
-
- GpiOpenSegment (hps, lSegmentName) ;
- {
- GpiSetColor (hps, CLR_RED) ;
-
- GpiCharStringAt (hps, &ptlTextStart,
- lTextLength, szText) ;
-
- GpiSetLineType (hps, LINETYPE_DOT) ;
-
- GpiMove (hps,aptlLineStart + TXTBOX_BOTTOMLEFT) ;
- ptl.x = 0 ;
- ptl.y = 0 ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps,aptlLineStart + TXTBOX_BOTTOMRIGHT);
- ptl.x = cxClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
- ptl.y = cyClient ;
- GpiLine (hps, &ptl) ;
-
- GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
- ptl.x = 0 ;
- GpiLine (hps, &ptl) ;
- }
- GpiCloseSegment (hps) ;
- break ;
-
- case WM_PAINT:
- /* WinBeginPaint (hwnd, hps, NULL) ; */
-
- GpiDrawChain (hps) ;
-
- /* WinEndPaint (hps) ; */
-
- WinValidateRect (hwnd, NULL, FALSE) ; /* temp fix */
- break ;
-
- case WM_DESTROY:
- GpiDeleteSegment (hps, lSegmentName) ;
- GpiAssociate (hps, NULL) ;
- GpiDestroyPS (hps) ;
- break ;
-
- default:
- return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
- }
- return FALSE ;
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- Using OS/2 Semaphores to Coordinate Concurrent Threads of Execution
-
- Kevin Ruddell
-
- OS/2 semaphores are a powerful and general mechanism for coordinating
- multiple concurrent threads of execution. One common use of semaphores
- is to serialize access to pieces of code and the resources referenced by
- the code. The other common use of semaphores is to allow one thread to
- signal another thread that an event has occurred, for example, that an I/O
- operation has been completed. The OS/2 systems fully support both these
- uses of semaphores.
-
- There are four types of semaphores in OS/2: exclusive system,
- nonexclusive system, RAM, and FSRam (Fast-Safe Ram). FSRam semaphores are
- a new addition to Version 1.1 of OS/2. Nonexclusive system and RAM
- semaphores can be used either for serialization or signaling, while
- exclusive system and FSRam semaphores are intended only for
- serialization. FSRam semaphores are intermediate in safety and their
- performance is comparable to that of RAM semaphores.
-
- System semaphores can be used by threads of different processes that do
- not share memory and they offer some safety advantages over the other two
- types. Future versions of OS/2 will provide additional protection
- mechanisms and network support for system semaphores.
-
- When access to a reusable resource, such as a file, a data structure, or
- the screen must be serialized, we associate a semaphore with the resource
- and require that the semaphore have only one owner at a time. A thread
- then attempts to gain ownership of a semaphore by issuing a DosSemRequest
- or DosFSRamSemRequest call. If the semaphore is unowned, the call returns
- a value of zero and the calling thread now owns the semaphore. When the
- thread is already owned, the call either returns immediately (if the
- Timeout parameter is zero) or else waits n milliseconds (if Timeout = n)
- or it waits indefinitely (if Timeout = -1). We say the thread is blocked
- if it must wait for the completion of a semaphore request. If the
- requesting thread gains ownership of the semaphore before the timeout
- period expires, it immediately returns a zero value. Otherwise, it waits
- until the timeout period has elapsed and returns a non-zero value. A
- thread surrenders ownership of a semaphore by issuing a DosSemClear or
- DosFSRamSemClear call.
-
- FSRam and exclusive system semaphores (system semaphores where the
- NoExclusive option was not selected in the DosCreateSem request that
- created the semaphore) are well suited to serialization. RAM semaphores
- and nonexclusive system semaphores can provide serialization, but you
- should follow some guidelines when using them.
-
- Whether semaphores are used for serialization or for signaling, they can
- cause the hazards of deadlock, violations of serialization, and process
- termination while owning resources. By careful analysis of the
- application, knowledge of the OS/2 semaphore functions, and employment
- of some simple rules, these hazards can be avoided.
-
- ───────────────────────────────────────────────────────────────────────────
- Basic semaphore operation for serialization of reusable resources.
- ───────────────────────────────────────────────────────────────────────────
-
- ──────────────────
- ─┐ ╔═══════════╗ DosSemRequest
- sem={1,2,...n}├─╢THREAD{sem}╟─── DosFSRamSemRequest
- │ ║issues call║ ────────┬─────────
- THREAD{1}│ ╚═══════════╝
- THREAD{2}│ R ───────────────────┐
- ∙ │ │
- ∙ │ THREAD{sem} owns R │
- ∙ │ ─────────────────────────────────── │
- THREAD{n}│ When THREAD{sem} owns the resource │
- ─┘ all other threads requesting access │
- │ to R are blocked │
- ──────────────────┬──────────────── │
- │ ╔═════════════════╗ │
- ║THREAD{sem} issues║ │
- │ ║ DosSemClear ║ │
- ╔═════════════════════╗ ║ DosFSRamSemClear ║ │
- ║ THREAD{sem} either ║ ╚══════════════════╝
- ║ reissues a request ║ ──────────────────────── ╔════════════╗
- ───║ for R or goes off ║───THREAD{sem} relinquishes───║ R ║
- ║to do something else.║ ownership of R ║ becomes ║
- ╚═════════════════════╝ ──────────────────────── ║ availiable ║
- R = Reusable Resource and an associated semaphore ╚════════════╝
-
-
- Semaphore Types
-
- When you write a program with semaphores, you should ask yourself two
- questions: Is this semaphore for serialization or signaling? Will it be
- used between threads of one process or between different processes? The
- answers to these questions indicate which type of semaphore you should
- use and the system calls with which you will access it. Generally, you
- should use RAM semaphores for serialization of or synchronization
- between threads within one process, FSRam semaphores or exclusive system
- semaphores for serialization of threads in different processes, and
- nonexclusive system or RAM semaphores for signaling between threads in
- different processes.
-
- All OS/2 semaphore operations are performed on a semaphore handle,
- which is a 32-bit quantity used to access the semaphore. In the case of
- a RAM or FSRam semaphore, the handle is the semaphore data structure's
- address; in the case of a system semaphore, it is the value that is
- returned by the DosCreateSem or by the DosOpenSem call.
-
-
- RAM Semaphores
-
- RAM semaphores are mainly useful for coordinating the execution of
- threads within a process. They are fast, since they use a simple data
- structure, a double word of data storage, and receive relatively little
- management or protection from OS/2.
-
- RAM semaphores can be used between processes that share memory, which
- could be created by a DosAllocShrSeg call. However, RAM semaphores should
- not be used for serialization between processes, since OS/2 will not free
- a RAM semaphore if the owner terminates while owning it. They are fine,
- though, for providing serialization between threads within one process
- and can be employed for signaling within or between processes.
-
- The RAM semaphore data structure is a double word of storage that must be
- initialized to 0 (unowned/clear) before it is used as a semaphore. It
- should then be accessed using any of the following: DosSemClear,
- DosSemRequest, DosSemSet, DosSemSetWait, DosSemWait, or DosMuxSemWait.
-
-
- System Semaphores
-
- System semaphores are the most flexible, safe, and easy-to-use type of
- semaphore, but the overhead needed to maintain them exacts a performance
- penalty. They do not require shared memory, and their ownership is
- relinquished when their owner terminates, so they are suitable for
- serialization between processes.
-
- Each process that wishes to use a system semaphore must get a handle from
- OS/2 by making a DosCreateSem or DosOpenSem call. The process supplies a
- null-terminated semaphore name with the same format as the name of a file
- in the subdirectory \SEM\, for example, \SEM\RESOURCE.LCK. OS/2 then
- returns a handle for future access. The process also specifies whether
- nonowning processes can alter the state of the semaphore while it is
- owned. System semaphores are usually created nonexclusive for signaling
- applications and exclusive for serialization applications.
-
- DosCreateSem is used if the semaphore does not already exist (an error
- code is returned if it exists) and initializes the new semaphore to
- unowned. DosOpenSem is used if another process has created the system
- semaphore, and an error code is returned if it does not exist. If, in your
- application, one process will definitely run first, it can issue
- DosCreateSem; later processes can then issue DosOpenSem. If the order of
- execution of processes is not known, each one can call DosCreateSem and,
- if this fails because the semaphore already exists, then call DosOpenSem.
-
- When a process no longer needs to reference a system semaphore, it should
- issue a DosCloseSem call. The system semaphore itself will be deleted when
- all of the processes using the semaphore have called DosCloseSem. If a
- process terminates with open system semaphores, then the semaphores are
- closed by the system (see Figure 1).
-
- Normally, a thread should not request a semaphore while it already owns
- it. This would cause it to block waiting for itself to free the semaphore,
- resulting in deadlock.
-
- A system semaphore created with the exclusive option does not cause a
- thread to block if the thread makes a DosSemRequest while owning the
- semaphore. Instead, the semaphore's use count is incremented. Each time
- the owning thread issues a DosSemClear, the semaphore's use count is
- decremented, and when the use count becomes 0, the semaphore is released.
- Note that such a semaphore is not the same as a classical counting
- semaphore, which will be simulated later in this article. If another
- thread attempts to do a DosSemRequest on an exclusive semaphore that is
- already owned, it will block and wait for the use count to go to 0.
-
- Nonexclusive system semaphores behave just as RAM semaphores do when they
- are used with DosSemClear, DosSemRequest, DosSemSet, DosSemSetWait,
- DosSemWait, and DosMuxSemWait.
-
-
- FSRam Semaphores
-
- FSRam semaphores provide serialization between processes, combining most
- of the speed of RAM semaphores with much of the safety of system
- semaphores. They use a simple data structure and have a small system
- overhead, so their performance is very good. Since they store the data to
- track semaphore ownership properly, FSRam semaphores permit DosExitList
- processing to free an owned resource when the owning process terminates.
-
- Just as with exclusive system semaphores, FSRam semaphores support
- recursive DosFSRamSemRequest's by incrementing a use count, and support
- DosFSRamSemClear's by decrementing a use count. When the use count goes
- to 0, the semaphore becomes unowned. (Again, note that such a semaphore is
- different from a classical counting semaphore.) DosFSRamSemRequest and
- DosFSRamSemClear are the only OS/2 calls available for FSRam semaphores.
-
- The FSRam semaphore data structure exists in memory shared by the
- cooperating processes and must have its length field set to 12 and all of
- its other fields initialized to 0 before its first use.
-
- ───────────────────────────────────────────────────────────────────────────
- Comparison of Semaphore Types
- ───────────────────────────────────────────────────────────────────────────
-
- ┌────────────────────────────────────────────────────────────────────┐
- │Semaphore │ │ │ │█
- │Types │ Use │Protection │Performance │█
- ├──────────┼─────────────┬─────────────────┼────────────┼────────────┤█
- │ │ │Between Threads │ │ │█
- │Exclusive │Serialization│in Different │Some Safety │ │█
- │System │Only │Processes │Provided │ │█
- ├──────────┼─────────────┼─────────────────┤Managed │Moderate │█
- │Non- │Serialization│Between Threads │by OS/2 │ │█
- │exclusive ├─────────────┤in Different │ │ │█
- │System │Signaling │Processes │ │ │█
- ├──────────┼─────────────┼─────────────────┼────────────┼────────────┤█
- │ │ │Between Threads │ │ │█
- │ │Serialization│Within One │Minimal │ │█
- │ │ │Process │Protection──│ │█
- │RAM ├─────────────┼─────────────────┤Not │High │█
- │ │ │Between Threads │Managed │ │█
- │ │Signaling │in the Same or │by OS/2 │ │█
- │ │ │Different Process│ │ │█
- ├──────────┼─────────────┼─────────────────┼────────────┼────────────┤█
- │ │ │ │Some │ │█
- │FSRam │Serialization│Between Threads │Protection──│ │█
- │(Fast-Safe│Only │in Different │Partially │High │█
- │Ram) │ │Processes │Managed │ │█
- │ │ │ │by OS/2 │ │█
- └──────────┴─────────────┴─────────────────┴────────────┴────────────┘█
- ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-
-
- Serialization
-
- In OS/2, semaphores can ensure that no more than one thread at a time has
- access to a protected resource. This is done by assigning a semaphore to
- the protected resource and adopting the programming convention that each
- piece of code that accesses the protected resource must be preceded by a
- call to DosSemRequest (or one to DosFSRamSemRequest) and must be followed
- by a call to DosSemClear (or one to DosFSRamSemClear). It is essential
- that every thread accessing the protected resource follow this procedure,
- or time-dependent errors may occur.
-
- The thread whose semaphore request returns without error or time-out
- "owns" the semaphore, and all other threads that issue a similar request
- will block. This permits the owning thread to have exclusive access to the
- code that affects the protected resource. When the owning thread no longer
- needs to access the protected resource, it issues a semaphore clear and
- thereby gives up its ownership. If another thread is blocked because of a
- request on the newly released semaphore, it is permitted to proceed and
- access the protected resource.
-
- For example, suppose two threads use a semaphore to serialize access to a
- resource. You might end up with code such as that shown in Figure 2, where
- the ──1 indicates that the calling thread will wait indefinitely until the
- requested semaphore becomes available.
-
- The programming convention described previously should always be strictly
- followed unless it is unnecessary for a particular application. Even a
- slight deviation can lead to a failure of serialization, deadlock, or
- both.
-
- An FSRam semaphore can be safely used for serialization between threads of
- different processes if each process provides for recovery in case it
- terminates while owning the semaphore. Before terminating, each process
- should set up an ExitList routine to ensure the integrity of the resource
- protected by the semaphore by making a call to DosExitList. When the
- process terminates, the ExitList routine will execute and should first
- issue DosFSRamSemRequest to acquire the semaphore, then clean up the
- resource, and finally issue DosFSRamSemClear in order to free the
- resource for use by other processes.
-
- If the FSRam semaphore is owned by a thread of the terminating process,
- at the point when DosFSRamSemRequest is issued during the execution of an
- ExitList routine, the owning thread ID is forced to become the current
- thread ID and the use count for the semaphore is set to 1. This allows the
- ExitList routine to put the resource into a consistent state and then
- free it with DosFSRamSemClear (see Figure 3).
-
- RAM semaphores should only be used to serialize threads within a single
- process. In this case, they are efficient (since there is little system
- overhead), convenient (since the threads inherently share memory), and
- safe (since the process only ends when all of its threads end).
-
- It is possible to use a nonexclusive system semaphore for serialization,
- but all threads that access the semaphore must follow the programming
- conventions for serialization. Nonexclusive system semaphores
- generally behave in the same way RAM semaphores do, except that if the
- owner dies while owning one, the next thread to return from a
- DosSemRequest on it will own it and get an error code. The new owner, an
- ordinary thread or an ExitList routine, can clean up the resource and
- issue DosSemClear.
-
- Exclusive system semaphores can be safely used to provide serialization
- between different processes, since the system semaphore data structure
- contains the ID of the owning thread, and any attempt by one thread to
- change the state of an exclusive system semaphore owned by another thread
- will cause the thread to block or report an error.
-
- When a thread ends, either individually or because its process
- terminates, while owning a system semaphore, a flag is set in the
- semaphore data structure. The next thread that gets the semaphore by means
- of a call to DosSemRequest gets the error code "owner ended owning
- semaphore" and can take suitable recovery measures. A subsequent call to
- DosSemClear will free the semaphore and clear the error flag.
-
- ExitList processing for system semaphores used for serialization is very
- similar to the ExitList processing for FSRam semaphores.
-
-
- Signaling
-
- RAM or nonexclusive system semaphores allow one thread to detect an event
- in another . For example, suppose code fragment F1 in thread1 must
- execute before F2 in thread2, and the system semaphore with handle sigSem
- has been created with the NoExclusive option and has been set with
- DosSemSet. The two threads could appear as shown in Figure 4.
-
- In the worst case, thread2 will execute the call to DosSemWait before
- thread1 finishes executing F1, at which time it blocks. When thread1
- finishes executing F1, it clears sigSem, and thread2 proceeds to execute
- F2.
-
- The semaphore functions DosSemSet, DosSemClear, DosSemWait,
- DosSemSetWait, and DosMuxSemWait are used for signaling. DosSemSet sets a
- semaphore, and DosSemClear clears it. DosSemWait blocks the current thread
- until the semaphore is cleared or until an optional time-out interval has
- elapsed. DosSemSetWait is an indivisible concatenation of DosSemSet
- followed by DosSemWait. DosMuxSemWait blocks the current thread until one
- of a list of semaphores is cleared or until an optional time-out interval
- is over.
-
- You should not use the signaling calls with exclusive system semaphores.
- As an example, consider a thread that issues DosSemSetWait. If the
- exclusive system semaphore is already owned, an error is returned.
- Otherwise, the thread sets the semaphore and waits for it to clear.
- However, this will never happen since no other thread can clear it.
-
- Figure 5 is an example of the use of DosSemSetWait, similar to the one
- mentioned above with DosSemWait but this time in an infinite loop. The
- code fragment F2 can execute only once for each execution of F1.
-
- Figure 6 demonstrates the use of DosMuxSemWait. In this case, thread4
- blocks until thread1, thread2, or thread3 clears its semaphore. Thread4
- proceeds and takes whatever action is appropriate in response to the
- signaling thread.
-
- As with serialization, a thorough analysis is necessary when using
- semaphores to implement signaling in order to avoid the hazards of deadlock
- and synchronization failure.
-
-
- Circular Buffers
-
- Suppose two threads of one process are communicating via a circular
- buffer, that is, thread1 writes to Buffer and thread2 reads from Buffer.
- Buffer has a fixed capacity, bufSize, and pointers to the locations of the
- newest (head) and oldest (tail) items. The buffer is circular in the sense
- that each thread accesses location 0 after it accesses location bufSize──1.
- Buffer is empty when head=tail and is full when tail=head+1 (mod bufSize).
-
- Suppose that mutexSem, emptySem, and fullSem are RAM semaphores and that
-
- DosSemClear(&mutexSem);
- DosSemSet(&emptySem);
- DosSemClear(&fullSem);
- head=tail=0;
-
- is executed before the threads are spun off. Then the two threads would
- appear as shown in Figure 7.
-
- The protected resource in this case is Buffer, together with its
- associated variables (head, tail, emptySem, and fullSem). Each thread
- accesses the protected resource between calls to DosSemRequest and to
- DosSemClear on the mutual exclusion semaphore, known as mutexSem, so the
- accesses are made serially, and there are no race conditions.
-
- Before the threads are spun off, the protected resource is in a consistent
- state: head==tail, emptySem is set, and fullSem is clear. Each thread
- changes the state of the protected resource so that if it was consistent
- before entering the critical section, it is consistent on leaving the
- critical section. For example, if thread1 adds an item that occupies the
- last available free space in Buffer, it then sets the fullSem semaphore.
- Thread1 always clears the emptySem semaphore since it has just added an
- item to Buffer. Similarly, thread2 sets the emptySem semaphore when it
- removes the last item from the buffer and always clears the fullSem
- semaphore.
-
- If Buffer is full, thread1 sets fullSem and then blocks on fullSem. In
- this case, emptySem is not set and, since thread1 is blocked outside its
- critical region, thread2 can enter its critical region, remove an item
- from the buffer, and clear fullSem, allowing thread1 to proceed.
-
- If Buffer is empty, thread2 sets emptySem and then blocks on emptySem.
- Since fullSem is not set and thread2 is blocked outside its critical
- section, thread1 can enter its critical section, add an item to Buffer,
- and clear emptySem, allowing thread2 to proceed.
-
-
- Simulating a Classical Counting Semaphore
-
- A classical counting semaphore can be easily simulated with the
- semaphore facilities that are offered by OS/2. ClassicCountSem is an
- integer variable that can only be accessed, apart from initialization,
- via the two atomic operations P and V (see Figure 8).
-
- To simulate classicCountSem and its associated operations P and V, use the
- OS/2 semaphores countSem for signaling and mutexSem for serialization,
- both initially clear, and the integer variable count, initially
- nonnegative (see Figure 9).
-
- All of the code in the P and V routines that changes count and countSem
- lies in mutual exclusion regions bracketed by calls on mutexSem,
- simplifying the analysis of these routines.
-
- These routines would work correctly with all references to countSem
- removed, but P would burn up a lot of cycles testing conditions that are
- not satisfied. The wait on countSem prevents useless activity, but it also
- allows P to proceed if there is a chance that it will run to completion. P
- will block on countSem only if count is 0. When V executes, count is
- incremented and countSem is cleared, permitting P to run until count is
- found to be 0 again. Therefore, whenever V "produces" a unit, any waiting
- P's have an opportunity to "consume" it.
-
-
- Scheduling
-
- The precise timing and behavior of threads synchronized by means of
- semaphores is affected by the activity of other threads in the system,
- such as priority and position in the list of blocked threads. If several
- threads are waiting on a semaphore, your application should not count on
- one in particular to be scheduled to run, as factors outside your control
- may affect the decision of the scheduler.
-
- DosSemWait, DosSemSetWait, and DosSemRequest are level-triggered. This
- means, for example, that a thread blocked by one of these calls will only
- return if the blocking semaphore remains clear until the blocked thread
- can actually run. If some other thread, in the meantime, sets the
- semaphore again, then the blocked thread will remain blocked. If the
- semaphore remains clear until the blocked thread can be scheduled and
- actually run, then the call returns and the once-blocked thread continues
- with its execution.
-
- DosMuxSemWait, on the other hand, is edge-triggered. That is, a thread
- that is blocked by a call to DosMuxSemWait will return if one of the
- semaphores in its list is cleared, even if the semaphore is set by another
- thread before the blocked thread can run again.
-
-
- Conclusion
-
- Semaphores in OS/2 can be used to provide serialized access to protected
- resources by associating a semaphore with the resource and combining OS/2
- function calls with programming conventions to ensure that the semaphore
- has only one owner at a time. Semaphores can also allow one thread to
- signal another that an event has occurred. By analyzing the particular
- application, you can pick the appropriate semaphore type and OS/2
- function calls.
-
-
- Figure 1: Using System Semaphores
-
- if( rc=DosCreateSem( NoExclusive, &sysSem, &semName))
- if( rc == ERROR_ALREADY_EXISTS)
- DosOpenSem( &sysSem, &semName) ;
- else
- <...error...>
-
- <...use the semaphore...>
-
- DosCloseSem( sysSem) ;
-
-
- Figure 2: Two Threads Serializing Access to a Resource
-
- long resourceSem = 0;
-
- thread1()
- {
- ∙
- ∙
- ∙
- DosSemRequest( &resourceSem, -1L);
-
- <... read/modify resource ...>
-
- DosSemClear( &resourceSem);
- ∙
- ∙
- ∙
- }
-
- thread2()
- {
- ∙
- ∙
- ∙
- DosSemRequest( &resourceSem, -1L);
-
- <... read/modify resource...>
-
- DosSemClear( &resourceSem);
- ∙
- ∙
- ∙
- }
-
-
- Figure 3: ExitList Processing for a Fast-Safe Ram Semaphore
-
- main()
- {
- ∙
- ∙
- ∙
- DosExitList( 1, &Cleanup); /* add to exit list */
- ∙
- ∙
- ∙
- }
-
- Cleanup()
- {
- if( DosFSRamSemRequest( &sem, 0L) != ERR_TIMEOUT)
- {
- <... put in a consistent state ...>
- DosFSRamSemClear( &sem);
- }
- DosExitList( 3, 0); /* goto next ExitList routine */
- }
-
-
- Figure 4: Signaling Between Threads
-
- DosSemSet( sigSem);
-
- thread1()
- ∙
- ∙
- ∙
- F1
- DosSemClear( sigSem);
- ∙
- ∙
- ∙
- }
-
- thread2()
- ∙
- ∙
- ∙
- DosSemWait( sigSem, -1L);
- F2
- ∙
- ∙
- ∙
- }
-
-
- Figure 5: Using DosSemSetWait
-
- thread1()
- ∙
- ∙
- ∙
- while(1)
- {
- F1
- DosSemClear( sigSem) ;
- }
- ∙
- ∙
- ∙
- }
-
- thread2()
- ∙
- ∙
- ∙
- while(1)
- {
- DosSemSetWait( segSem, -1L) ;
- F2
- }
- ∙
- ∙
- ∙
- }
-
-
- Figure 6: Using DosMuxSemWait
-
- struct {
- int numSem;
- int res1;
- unsigned long semHandle1;
- int res2;
- unsigned long semHandle2;
- int res3;
- unsigned long semHandle3;
- } muxSemList ;
-
- int muxIndex;
-
- thread1()
- ∙
- ∙
- ∙
- F1
- DosSemClear( sigSem1) ;
- ∙
- ∙
- ∙
- }
-
- thread2()
- ∙
- ∙
- ∙
-
- F2
- DosSemClear( sigSem2) ;
- ∙
- ∙
- ∙
- }
-
- thread3()
- ∙
- ∙
- ∙
- F3
- DosSemClear( sigSem3) ;
- ∙
- ∙
- ∙
- }
-
- thread4()
- ∙
- ∙
- ∙
-
- muxSemList.numSem = 3 ;
- muxSemList.res1 = 0;
- muxSemList.semHandle1 = sigSem1 ;
- muxSemList.res2 = 0 ;
- muxSemList.semHandle2 = sigSem2 ;
- muxSemList.res3 = 0 ;
- muxSemList.semHandle3 = sigSem3 ;
- ∙
- ∙
- ∙
- DosMuxSemWait( &muxIndex, &muxSemList, -1L) ;
- switch( muxIndex) {
- case 1:
- ∙
- ∙ /* respond to F1 */
- ∙
- case 2:
- ∙
- ∙ /* respond to F2 */
- ∙
- case 3:
- ∙
- ∙ /* respond to F3 */
- ∙
- }
- }
-
-
- Figure 7: Example of a Circular Buffer
-
- thread1()
- ∙
- ∙
- ∙
- <... get item c ...>
- DosSemWait( &fullSem, -1L) ;
-
- DosSemRequest( &mutexSem, -1L);
- Buffer.head = c; /* store c in Buffer */
- head++; /* advance head of Buffer */
- head %= bufSize; /* wrap around to beginning */
- if((head==tail)||((tail==0)&&(head==bufSize-1)))
- DosSemSet( &fullSem); /* set if full */
- DosSemClear( &emptySem); /* not empty */
- DosSemClear( &mutexSem);
- ∙
- ∙
- ∙
- }
-
- thread2()
- ∙
- ∙
- ∙
- DosSemWait( &emptySem, -1L);
-
- DosSemRequest( &mutexSem, -1L);
- c = Buffer.tail; /* get c from Buffer */
- tail++; /* advance tail of Buffer */
- tail %= bufSize; /* wrap around to beginning */
- if( head==tail)
- DosSemSet( &emptySem); /* set if empty */
- DosSemClear( &fullSem); /* not full */
- DosSemClear( &mutexSem);
-
- <... use item c ...>
- ∙
- ∙
- ∙
- }
-
-
- Figure 8: Classical Counting Semaphore
-
- P()
- {
- <... wait until classicCountSem > 0 ...>
- classicCountSem-;
- }
-
- V()
- {
- classicCountSem++;
- }
-
-
- Figure 9: Simulating a Counting Semaphore under OS/2
-
- P()
- {
- int blocked=1;
-
- while( blocked == 1)
- {
- DosSemWait( &countSem); /* wait til maybe ok */
-
- DosSemRequest( &mutexSem, -1L); /* mutual excl */
- if( count == 0) /* not ready yet */
- DosSemSet( &countSem); /* set up block */
- else {
- count-; /* decrement count */
- blocked-; /* set up loop exit */
- }
- DosSemClear( &mutexSem); /* mutual excl */
- }
- }
-
- V()
- {
- DosSemRequest( &mutexSem, -1L); /* mutual excl */
- count++; /* increment count */
- DosSemClear( &countSem); /* free waiters */
- DosSemClear( &mutexSem); /* mutual excl */
- }
-
- ████████████████████████████████████████████████████████████████████████████
-
- Design Concepts and Considerations in Building an OS/2 Dynamic-Link Library
-
- Ross M. Greenburg
-
- You are in a maze of twisty little passages, all alike.
-
- >USE DYNAMIC-LINK LIBRARY
-
- I see no DLL here.
-
- >MAKE DLL
-
- I don't know how to make a DLL.
-
- >INVENTORY
-
- You have:
-
- ■ an 80286 machine
- ■ sufficient memory
- ■ OS/2
- ■ An OS/2 toolkit. In the toolkit is:
- ■ a text editor
- ■ a C compiler
- ■ an assembler
- ■ a linker
-
- >USE TOOLS IN TOOLKIT TO MAKE DLL
-
- Huh?
-
- Just as in the game ADVENTURE, not knowing the keywords when you're
- trying to create a dynamic-link library can be very frustrating. Once you
- know the keywords, though, you can explore new areas of the game and bring
- home prizes and treasures. The objective of this article is to help teach
- you some of the new keywords and techniques you need to build a dynamic-
- link library of your own.
-
-
- What Is a DLL?
-
- The idea of dynamic-link libraries is one of the most important
- concepts that the OS/2 systems introduce. Although the principle of DLLs
- has been around for some time-they are usually called shareable libraries
- in other operating systems-OS/2 makes such a shareable library an
- intrinsic part of the operating system. In fact, the system library
- functions themselves are DLLs in OS/2, and are clearly separated by
- device type, making upgrading easy.
-
- Under MS-DOS(R), after you compile a program, you then link it with other
- portions of the program and with portions from a library of commonly used
- routines. The end result is a standalone piece of code that is loaded
- into memory, has its outstanding address references resolved, and is then
- executed. The physical file resulting from the link contains portions of
- the library it used; two programs that use printf will each contain a copy
- of the library functions that comprise that ubiquitous function.
-
- In a single-tasking operating system, with a sufficiently large hard disk,
- this really isn't a problem. In a multitasking operating system that
- allows for shared memory use, loading multiple copies of the same code
- seems wasteful. OS/2 obviates this by permitting only one copy of a given
- function to be loaded in memory and to have this copy shared by any task
- that wants to use it. Since the function itself is not physically part of
- the program file, it is possible for the executable to be rather small
- and to update the library only as required. The concept of separate
- overlay files, and complicated linkers, is no longer needed; just include
- the specific DLLs needed and let the operating system do the rest.
-
- However, the advantages of using DLLs go far beyond the convenience; there
- is a functionality to DLLs that you can exploit in many different ways.
- For example, there is the ability for two or more completely separate
- and distinct programs to share memory by simply accessing the same run-
- time routine. This communication, already an intrinsic part of OS/2, can
- be fine-tuned with DLLs to fit your exact needs in a complicated
- environment.
-
- This is not without a price, though; there are some tricks, cautions, and
- caveats to writing an operable DLL. I discovered some of them the hard way
- while preparing a simple example of DLL use. Of course, if you want to
- avoid the supposed complexity of these useful techniques, there is
- nothing in OS/2 to prevent you from using the standard, and more
- familiar, linking techniques of the past, except, perhaps, the knowledge
- that there is a better way.
-
- A dynamic-link library is not simply a new method of linking an old
- library. There are some intrinsic differences between the two techniques.
- A look at how statically linked libraries are linked into your code will
- help you to understand how the new Dynalink approach differs and why it is
- an improvement.
-
-
- Static Linking
-
- When you compile a standard C program, the resulting output of the
- compiler is an object file, which contains a number of different types of
- records for procedures and routines, externally accessible variables,
- local stack variables, and so on. Each item has a unique record type
- associated with it.
-
- There is also a unique record type that indicates where the code for a
- routine starts. Another record type has the name of the routine and a
- pointer to that routine's code. Some record types indicate that the
- routine requested is not found and hence must be external to the object
- module; other record types indicate that the object is an externally
- located data item.
-
- Each call to a routine that cannot be resolved within the particular
- source module is changed into a parameter that simply involves an
- "external item" record.You can help the compiler by specifying the call
- type as being a near or far routine, or a near or far external item of
- some other type.
-
- After you've finished compiling all your various source modules into
- their object modules, you link them together, along with some
- appropriate libraries, and end up with an executable piece of code. The
- linker examines each object module it sees, usually in the order in which
- they're presented, and keeps a list of all record types that either
- indicate a request for an external item or define a global item. Then, as
- it sees record types indicating actual code routines, it determines that
- routine's placement and resolves all calls for it into an actual address
- within the eventual output file. Far calls, of course, indicate not
- just an offset within a 64Kb segment, but allow additional segments to be
- addressed, which in turn permit the creation of much larger programs.
-
- There is really nothing intrinsically foreign to the compiler in the
- concept of mixed memory-model code as long as it knows how a routine will
- be called. The compiler generates a far return for routines defined as far
- calls and near returns for near calls. Addressing far data items is
- resolved in a similar fashion: the compiler puts out a record type that
- the linker can understand and resolve into an actual segment and offset.
- There is an extra step for the actual loading and executing of the code,
- discussed later in this article.
-
- Whatever items are not resolved within the linking of the various object
- modules are next searched for in the libraries. These libraries are
- basically object modules with nothing but local references resolved. A
- module in the library usually starts off as a simple object file and is
- then stored and indexed into a library as an entire unit. It is not stored
- on a routine-by-routine basis, but on an object-file-by-object-file basis
- instead.
-
- The appropriate routine or external item is found in the library, and the
- module is then pulled from the library and inserted into the executable
- form. All references to it are resolved, and the process continues. An
- important consideration is that the object module originally loaded
- into the library as one unit is pulled from it as one unit as well, even
- if only one of the functions specified in the routine is referenced.
-
- The end result is a totally self-contained image, which, when executed, is
- loaded at some address, called the base address. The base segment address
- is added to all of the other segment addresses throughout the code in the
- mysterious load routines, and finally, with one simple call or jump, your
- program is executed. That's a basic, not-too-technical description of how
- static linking works.
-
- The differences between dynamic linking and the idea of statically linking
- an already existing library are not all that substantial, but the end
- product is. Consequently, the conceptual design of the dynamic-link library
- is different.
-
-
- Dynamic Linking
-
- With a normal library, you compile all of the object modules you need and
- then use a librarian program to create a library. The library itself is in a
- strange format, suitable only for linkers and librarian programs.
-
- Things are a little different with DLLs, though. First, there are two
- separate link steps. You must link the constituent object file members
- that form the DLL together and then link your own code with the resulting
- DLL. Creating the DLL itself, however, requires some work.
-
- After you've created the object files that will make up DLL, you link them
- and a special module definition file with the normal linker to create
- DLL. Its format is really no different from a normal EXE file-it even
- has the "MARKZIBO" as its first two bytes. It is therefore well suited for
- the standard system loader to load as if it were actually a program.
- Later on, the system does just that for the initialization routine. The
- new library will have an extension of DLL, as this name is hardwired into
- any affiliated EXE files.
-
- The special module definition file, or DEF, describes the external
- interface for each of the accessible routines: their public names and
- their attributes. Anything not specifically mentioned in the DEF file
- cannot be routinely accessed by an outside program. This definition file
- is called the export module definition file.
-
- By running the export DEF file through a program called Import Librarian
- (IMPLIB), you can create a special library file. This library file is
- conceptually similar to the standard notion of a library and hence has
- the LIB extension (see Figure 1).
-
- Instead of providing a LIB file, another option is to create what is in
- essence the inverse of the export DEF file. Such a file is called an import
- module definition file, which also has the extension DEF (see Figure 2).
-
- When you link the DLL with your own code, the linker sees the special
- record format of the import library (the LIB file created by IMPLIB), or
- reads the import DEF file and creates special records that OS/2's program
- load facilities understand. The end result of a link using DLLs is a
- hybrid file that can be considered a partial EXE and a partial OBJ at the
- same time. A compiled object module resolves local variables and routines
- into a segment and an offset, leaving external references virtually
- undefined. The Dynalink program results in an EXE coming from the linker
- with its external references to DLL routines effectively unresolved.
-
- At this point, I'm going to refer to segments as selectors, since I am
- referring to DLLs implemented under OS/2 running in protected mode.
-
- Part of OS/2's program loader recognizes that the EXE it's about to load
- contains DLL calls. Finding these records causes a lookup on an internal
- table to determine if the DLL has already been loaded. Only one copy of
- any code within the DLL is loaded-all sessions and processes share this
- copy.
-
- Each module in the DLL is defined as a "load-at-run-time" or a "load-on-
- demand" module. Regardless of which definition is chosen, a selector is
- allocated for each module, and all references to those modules are now
- resolved into a selector and offset pair. If the module has been defined
- as a load-at-run-time module, then the actual code for the module is read
- from the file and loaded into memory, and any outstanding linkages are
- resolved. When trying to locate a DLL, by the way, the operating system
- looks in the directory paths specified in your environment's LIBPATH
- variable.
-
-
- Value of Protected Mode
-
- Consider what happens when a load-on-demand function is called before that
- selector points to valid code: a page fault occurs, and the memory
- management module can easily resolve what the problem is, load the
- appropriate code, and let the program continue operating as if nothing
- had happened. Subsequent calls to routines within the same selector would
- operate without a page fault. Once the page fault mechanism, an intrinsic
- part of OS/2 and protected mode applications, has been enabled, it is
- transparent whether or not a requested page exists in real memory or in
- virtual memory.
-
- The 80286 and 80386 chips have a table within them called the Local
- Descriptor Table, or LDT, which holds selectors and the characteristics of
- these selectors. There is an LDT for each of the processes currently
- running. If a process attempts to access memory by using a selector not
- within its LDT, the hardware will cause a fault to occur──effective
- hardware protection of memory space.
-
- The Global Descriptor Table, or GDT, is similar to the LDT (see Figure 3),
- except that all tasks can access the selectors, and their associated
- memory, contained therein. Although this seems like a simple way to make a
- selector and its data space accessible to multiple processes, OS/2 does
- not use the GDT for shared memory access; instead it makes an entry into
- the LDT of each process. This is because memory space used by a DLL should
- only be accessible to the processes actually using the DLL. Using the GDT
- would make the DLL selectors available to every session and process on
- the system.
-
- When a request for memory allocation is made to OS/2, the type of memory
- (shared or nonshared) is included in the request, and an entry is made in
- the LDT for all processes permitted to share this memory. Actually,
- whenever shareable memory is allocated, an entry is made in every
- process's LDT.
-
- Processes requiring access to the memory have proper entries in the LDT;
- other tasks have illegal contents that will cause a memory fault to be
- generated if they are used to access DLL-allocated memory. Only the
- kernel (Ring 0) code can write to these tables, however. (Device drivers
- also run at Ring 0, so they'd have write access to the descriptor tables
- as well.)
-
-
- What's the Big Deal?
-
- So far, a relatively efficient mechanism exists for linking in routines as
- required at run time instead of just once at link, all automatically and
- transparently. However, just what are the advantages of such an
- ability? First, swapping the DLL routines in and out of memory becomes
- pretty easy; the LDT has a "present" bit indicating whether the
- requested segment is in memory or not. If not in memory, a page fault
- occurs as described above, and the swapped-out DLL routine can be brought
- into real memory. Since the selector itself is just an index into a table
- that contains real address information, the individual DLL modules can
- end up anywhere in memory, transparent to your own code.
-
- Program code without some data space associated with it is a rarity; pure
- code can't manipulate items, although it is frequently useful in
- mathematical routines. The 8086/8088 family of processors used the data
- segment register to address its data space. The 80286/80386 family of
- chips requires data to be addressed through a selector as well. The
- information for the data selector is also stored in the LDT. By setting
- the appropriate bits in the LDT entry for a given selector, its associated
- memory can be made private or publicly accessible, or it can be set to be
- written to as read-only. Data selectors can even require a certain level
- of privilege in the code attempting to access it. Any illegal operation
- causes a fault to occur, and OS/2 can deal with the faulting process as
- required.
-
- This means that, with the LDT set properly, memory can be shareable
- between tasks and protected from illegal or erroneous access, and other
- interesting memory uses and control techniques are made possible.
-
- As such, the DLL can be controlled and fine-tuned in a variety of
- different ways, through the DEF files.
-
-
- Defining the DEF File
-
- There are two different types of DEF files. The EXPORT definition file
- lets the world know what the various entry points and their characteristics
- are. The IMPORT definition file indicates which functions of many potential
- DLLs will be used and should therefore be linked at run time. A DLL can have
- both IMPORTS and EXPORTS defined in the same DEF file.
-
- The IMPORT library is created by processing the EXPORT definition file
- through IMPLIB. Let's look at each piece separately, along with its
- available features and options. These options must all be entered in the
- appropriate DEF file in uppercase.
-
-
- LIBRARY Statement
-
- The EXPORT DEF file requires several fields (see Figure 4). The most
- important one that is required is the LIBRARY field, which defines the
- file as a DLL Export definition file, instead of a normal application DEF
- file.
-
- The LIBRARY statement must be the first one in the DEF file, allowing the
- linker (and IMPLIB) to have a head start on what is to come. The first
- argument, [name], to the LIBRARY statement is the eventual output name
- for the created DLL. The filetype, or extension, will automatically be
- DLL.
-
- When the DLL is first loaded, you may want to initialize some things, for
- instance, setting specific data items, and assuring that certain system
- resources are available. Each DLL can have an initialization routine that
- will be called when the DLL is first loaded and there are no other
- references to it, or upon each invocation of the DLL. The second argument,
- [init_type], lets you specify whether you want the initialization routine
- called every time the DLL is invoked (INITINSTANCE), or once, when the DLL
- is first loaded (INITGLOBAL, the default).
-
-
- NAME Statement
-
- Similarly, if the linker sees the NAME statement, it understands that you
- are creating an application and not a DLL. The NAME statement allows you
- to specify whether the application is Windows- or Presentation Manager-
- compatible and, if so, whether it is capable of running in real mode or
- protected mode. If you specify WINDOWAPI as the second argument, then the
- application requires Windows in order to execute. Specifying WINDOWCOMPAT
- means that it is Windows-compatible and can run in a window group under
- OS/2. Finally, specifying NOTWINDOWCOMPAT indicates that the application
- requires its own screen group when running.
-
- The name command allows you to specify with [appname] the name the
- application will have after linking. Naturally, the default extension is
- EXE. You'll get a warning message from the linker if the name you choose
- for the executable name and the name you choose in the NAME line don't
- match.
-
-
- CODE Statement
-
- All code segments within the DLL share a similar set of attributes unless
- otherwise specified. The default attributes are set with the CODE statement.
-
- There are a few other optional parameters that are allowed (but ignored)
- in the CODE statement for compatibility with Windows and the Presentation
- Manager.
-
- The [load] parameter indicates whether you want the segment
- automatically loaded upon DLL invocation (PRELOAD) or to wait until the
- segment is actually accessed with a call (LOADONCALL, the default). In an
- application with large areas of the code that might never be called, you
- no longer need to load those library calls into memory all at once. For
- example, if you had a very large error recovery routine that would only
- get called once in a great while, you wouldn't need to give up the memory
- space it required until the error occurred. Another example would be having
- many levels of help menus that are rarely used──a perfect fit for DLLs.
-
- When a LOADONCALL routine is called for the first time, it is loaded
- automatically and will stay loaded in memory unless it is marked as
- discardable. That means that its associated memory can be freed as required
- with fresh copies loaded from disk.
-
- If you use the [executeonly] option to specify that other processes
- cannot read this segment, then, even though the LDT marks the selector as
- globally accessible, it cannot be read or treated like a data segment
- selector by any process without the appropriate privilege level. The
- default, EXECUTEREAD, allows the memory allocated to this selector to be
- read for purposes other than execution.
-
- It is important to note that routines containing such items as a
- switch/case statement cannot be made EXECUTEONLY since a portion of the
- code space is used as data space for storage of the switch label table.
-
- Only code segments with a high enough privilege level can access the
- hardware directly. You can use the [iopl] parameter to specify that a
- segment has this ability. The default (NOIOPL) makes sense; unless
- otherwise specified, an attempt to access the hardware directly, for
- example the com port, will cause an immediate fault. When you allow a
- segment to access hardware directly, include the IOPL parameter in the
- CODE line. It is probably best to specify IOPL only when required. OS/2
- still requires that you make a system call to request the privilege of
- hardware access.
-
- The way privilege level functions in OS/2 is an important factor when
- trying to understand the implications of the IOPL parameter. The Intel
- programmer's reference manuals for the 80286 and 80386 provide a detailed
- description of the 80286/80386 IOPL levels.
-
- The 80286/80386 chip prohibits direct transitions between code segments
- with different levels of privilege. The default privilege level for
- application code in OS/2 is Ring 3. Ring 2 code segments can access
- hardware directly. The only way to transfer from one privilege level to
- another is through what is known as a call gate, which has a specific
- selector type in the LDT and an actual "destination selector," the
- selector belonging to the actual code segment of the privileged call.
- Also, the gateway has its own attendant privilege level and can only be
- called by code segments of the same privilege level.
-
- Conceptually, when a call is made to a privileged routine, it passes
- through the call gate before passing to the privileged routine. Since the
- only way through the call gate would be with either a CALL instruction
- (going into the routine) or a RET (coming back from the routine), the call
- gateway provides extra code security, but at the cost of some additional
- hardware overhead. Each transition via a gateway causes parameters on
- the stack to be copied to a new stack──another interesting security
- feature of the 80286, since a program with a lower privilege level could
- manipulate the return address on the stack otherwise.
-
- A routine utilizing the IOPL parameter uses up an additional slot in the
- LDT table. Although the LDT table has 8Kb of possible entries in it-each
- LDT entry takes up 8 bytes, so an entire segment has been allocated to
- the LDT in OS/2-5Kb of those are reserved for all shared segments
- throughout the entire system, including the selector space used for DLL
- selectors. This leaves you with about 3Kb of LDT entries for private use,
- which is probably enough for the foreseeable future. This has been
- modified to a 3-to-1 shared-to-private ratio in the next release of OS/2.
-
- The final parameter in the CODE statement allows you to specify whether
- the segment is a NONCONFORMING or a CONFORMING segment. This also deals
- with the IOPL privilege level and can be pretty confusing at first.
- Consider it to be the inverse of the gateway approach. Normally, a segment
- will execute with the privilege level of the calling segment. However,
- there are times when this might not be appropriate; consider a Ring 3
- communications protocol checking routine called from a Ring 0 device
- driver. In this situation, you might not want to allow the protocol
- checker to operate with the higher privilege of its calling segment. The
- default case, the NONCONFORMING parameter, would cause the Ring 3 routine
- to execute at Ring 3. Set to CONFORMING, it would execute at the privilege
- level of the routine calling it, the device driver running at Ring 0.
-
-
- Data Space Definitions
-
- Just as code segments have a method of setting default parameters, the
- data segments also allow certain parameters to be set with the DATA
- statement, which shares some of its parameter list with the CODE
- statement. This makes a great deal of sense because these parameters
- describe how to make the default settings for each data selector in the
- LDT. The format of the DATA statement is therefore very similar to the
- CODE statement.
-
- As before, [load] indicates whether the data segment should be loaded upon
- first invocation or when the first access to the selector's address is
- made. The default condition is LOADONCALL; however, you can specify
- invocations of load with PRELOAD.
-
- The [readonly] parameter helps you to determine whether the data segment
- can be written into with the default parameter of READWRITE or should be
- protected against write access (READONLY). Attempts to write to a READONLY
- segment cause a hardware fault.
-
- You use [instance] to specify whether or not the data segment, the DGROUP
- data segment in most cases, should be automatically allocated upon
- invocation, and if so, whether there should be one copy allocated for
- the entire DLL (SINGLE, the default setting for DLLs) or each instance of
- DLL use should have its own automatic data segment allocated (MULTIPLE,
- the default setting for applications). If no automatic allocation is
- required, then the parameter should be set to NONE.
-
- Each data segment can have its own IOPL level, which allows you to set the
- minimum privilege level required to access this data segment. Setting the
- [iopl] parameter to IOPL means that only Ring 2 and higher privileged
- levels are granted access to the data segment. The default, NOIOPL,
- enables Ring 3 code segment routines to have access to the data affiliated
- with the data segment. This allows you to create an interesting interface
- between IOPL and NOIOPL segments through common shared memory, like
- passing a message through a keyhole.
-
- Finally, [shared] allows you to determine whether or not a data segment
- marked as a READWRITE segment can be shared among different tasks. If it
- is marked as shareable, then only one segment is allocated at load time,
- and any process with a privilege level sufficient to write to it can do
- so. The default, NONSHARED, does not permit write access to a common data
- segment and causes a separate copy to be loaded for each instance. If a
- data segment is marked as READONLY, then by definition it is shareable.
-
-
- Segment Parameters
-
- Unless otherwise specified, code and data segments have the attributes
- you set in the CODE and DATA statements, or their predefined default
- values if you don't describe them. However, using the SEGMENTS statement,
- you can specify the individual characteristics for a given named segment.
-
- The [CLASS 'classname'] parameter is an option that lets you specify that
- the <segmentname> parameter, which is required, be assigned to the class
- specified. If you don't specify a classname, then the CODE classname will
- be assigned to code segments and "unclassed" data segments will be
- assigned to the DGROUP classname, or whatever was specified in the
- appropriate DATA group definition for the object/segment.
-
-
- EXPORTS Statement
-
- The EXPORTS statement is the only method of informing the outside world
- about the routines of the DLL and is applicable only to DLLs. Unless
- specified by inclusion in the EXPORTS section of the DEF file, a DLL routine
- is invisible to applications.
-
- A name used internally within the DLL need not be the name the application
- knows the routine by; you can easily make the outside name different from
- the internal name. This gives you a class of functions serving a similar
- purpose and then lets you categorize them with a meaningful prefix.
-
- If you want, you can allow access to the function by its ordinal (or the
- routines library "slot" number) instead of by its name, by specifying the
- desired ordinal, obviously unique for the DLL, preceded by an at (@)
- sign. If you do, lookups will be faster at load time, and less space will
- be required for the in-memory search list.
-
- There is a drawback to using ordinals, though; once you pick an ordinal
- number, you're stuck with it. This makes debugging a bit harder. Of
- course, the ordinal approach is good for DLLs whose internal structure
- you'd rather keep secret.
-
- If you do decide on the [@ordinal] option, then you may have to consider
- using the [RESIDENTNAME] option as well; normally, if an ordinal is used,
- OS/2 does not keep the specified external name available. If you don't use
- the ordinal parameter, OS/2 keeps the name resident in its search tables.
-
- If you include the use of any privileged functions in your routine, you
- have to let the linker know how many words to reserve for parameter
- copying by using the [pwords] variable. This information is later passed
- to the loader for construction of call gates. Since a calling task will
- have its own parameters copied as it passes through the gateway, you would
- have to reserve that space beforehand.
-
-
- IMPORTS Statement
-
- The IMPORTS section enables you to specify which external DLL routines you
- need in your application.
-
- Again, like the EXPORTS lines, you can specify a name your routine uses
- when it is trying to resolve external routines. You could, therefore,
- create a debugging DLL and a normal DLL and be able to link between them
- only by changing the <modulename> or the <entryname> associated with the
- named routine. The name of the application or DLL containing the desired
- <entryname> is the <modulename> specified in the EXPORTS statement for the
- DLL. The <entryname> can also be an ordinal number.
-
- If the optional [name=] parameter is not specified, then the default name
- by which the routine will be known will be the same as <entryname>. You
- must specify an internal name, however, if you've specified an ordinal
- number instead of an <entryname>.
-
- You can include other statements in the DEF file(s). They are summarized
- in Figure 5.
-
-
- Using the DEF Files
-
- There are two specific ways to use the DEF files: first, just include
- them on the command line to the linker, and second, pass them to the
- Import Library Manager utility, IMPLIB.
-
- IMPLIB is a standard part of the developer's toolkit in OS/2. If you're
- creating a DLL and the application to use that DLL, you don't necessarily
- need IMPLIB, since you can create the EXPORT and IMPORT library definition
- files as you desire. However, if you're creating a DLL for other
- applications to use, such as a commercial functions library or a
- replacement for a product that already exists, then IMPLIB should be part
- of your development cycle.
-
- IMPLIB takes one or more definition files for input and then produces what
- appears to be a simple LIB file for output. This allows you to include the
- LIB file in the link step. Assuming that you had two DLLs, called
- COM_INP.DLL and COM_OUT.DLL, each with their associated DEF files, you
- could create an IMPLIB statement such as the one shown in Figure 6.
-
- Then, simply distribute the COM_STUF.LIB and the two DLLs, keeping the
- internal details of the DLLs to yourself. Special versions of these import
- library files can allow for different public definitions of your DLL,
- for instance, one for production, and one for debugging.
-
-
- A DLL Example
-
- In attempting to create a DLL, I ran into a number of difficulties,
- some of which cannot, by the very nature of the DLL and multitasking
- software, be resolved. Deadlocks can occur in DLLs just as they do in
- other types of software.
-
- Think of the requirements of a multisession process such as a "chat"
- facility: multiple copies of the same process running, each of which
- occasionally generates a message, which is added to some internal queue.
- Each message generated must be collected by all other processes before it
- can be erased from the queue of outstanding messages, and each such message
- must eventually be displayed by each process. Finally, each process must be
- able to login or logout from the chat session and must have a unique
- identifier.
-
- I've designed such a facility, called DLL_CHAT (see Figure 7 for the code
- listings), as a method of demonstrating some of the unique abilities and
- problems that occur when using a DLL as the glue to hold a multiprocess
- concept like this together. It may not be useful on a single-screen
- machine, but if the output were going to a number of communications
- ports, it would be.
-
- You should note that all the problems inherent with this code design can
- be easily solved by including the OS/2 system resource of queues. However,
- that approach wasn't used, primarily because it wouldn't have required
- DLLs and wouldn't have been as efficient, either.
-
-
- Stepwise Design
-
- One advantage of using DLLs in this application is the ability not only to
- have private and shared memory, but for separately compiled and executed
- tasks to utilize the same code simultaneously. There is nothing to prevent
- users of DLL_CHAT from following my coding conventions and creating their
- own user-friendly interfaces──the bane of spiffy-concept designers. In
- fact, there is no reason why differently designed front ends couldn't be
- used for each session. Starting with this concept, I designed this code
- with most of the capabilities in the DLL.
-
- One ability of my DLL, called CHATLIB (see Figure 8 for code listings), is
- to provide for initialization code that is executed either on the
- first invocation of the DLL only, or on each invocation. This
- initialization routine is called before the process itself starts to
- run. This DLL only calls its initialization routine the first time, so its
- EXPORT file contains the INITGLOBAL parameter. Since this is the default
- condition, you can exclude it if you wish. The routine I use in this DLL
- is simple, setting certain default conditions and allocating some
- required queue space.
-
- First, the login procedure has to advise the library code that another
- consumer and provider of messages has suddenly appeared. To make things
- easier, the login procedure returns some user identifier to the process.
- It is useful to include an ID when generating new messages, when
- consuming old ones, and, of course, when logging out.
-
- The login procedure could have been part of the initialization
- routine, which would have been of type INITINSTANCE. However, since the
- initialization routine is run before the application gets control, I
- felt uneasy about logging in before the main routine had even been
- reached. As you can see, the initialization routine is used for things
- that I consider to have a "system" nature, while logging in seems to be
- more of an "application" task. However, this is only a personal
- sentiment.
-
- When the DLL sees the login, it allocates and assigns whatever global and
- local objects and structures are necessary for the new process. Where the
- actual allocations of memory would be made had to be decided in the
- design, since the memory could be allocated either in the DLL (becoming,
- in essence, a hidden object from the client code) or in the per-process
- code itself. There are advantages to having a DLL routine allocate memory
- that is globally accessible to all processes but that only the DLL
- routines know about.
-
- Also, a login causes each message already in the queue to appear unread to
- the newly logged-in task. Later, when requests are made for an
- outstanding and unread message, these messages will be returned.
-
- The general design of the DLL causes a sharing of the cleanup task on each
- call to the get-a-message routine. When a message is passed to the DLL, it
- is added to a queue, a structure that includes a flag word with one bit
- for each session. A mask word with a set bit for each empty task slot is
- used for the initial value of this flag word. An "or" operation is then
- used to obtain the current task ID, allowing the sender of the message to
- indicate that it has already received the message.
-
- When a process fetches a new message, it sets the bit in the flag word to
- show that it has done so. Then, when that flag indicates that all
- processes have received a copy of the message, the message can be removed
- from the global queue. Therefore, each process has to be able to
- manipulate that queue directly or must call a routine that has that
- ability. I've opted for a more modular design; using a routine to
- specifically remove the message from the queue, or to add a message to
- the queue, allows me to isolate the queue itself.
-
- Although the queue resides in global memory at this point, perhaps in the
- future it might reside on some node on a network or some memory device
- that might require a higher privilege level. Therefore, isolating the
- routine that physically modifies the queues is a good idea.
-
- Since there isn't a human attached to each session, each session sends a
- message only after a random amount of time has passed. And, just to keep
- things interesting, there is a suitable sleep period while the imaginary
- typist is entering his or her message, letting messages build up in the
- queue. Whenever the sender is not typing or sending a message, it is
- executing a loop that constantly seeks the outstanding message count.
- Blocking on a null message count would prohibit the sender from sending a
- message. Of course, OS/2 lets you have two different threads, one of which
- could block on a null message count within the DLL, but that is outside
- the scope of this article.
-
- Displaying of received messages occurs on a per-process basis. This
- could cause problems if the display takes place when the session is not
- the current foreground session; messages could scroll off the virtual
- screen while the process is in the background, an unacceptable condition
- in a real chat system.
-
- If a message is to be displayed but the session is not the foreground
- session, it is stored in an internal display queue. Normally, this queue
- would be a linked list of calloc'd memory. For DLL_CHAT, a short
- character array was used, one which would fill up quickly. Eventually,
- when it does fill up, it will stop fetching messages from the DLL queue.
- Since the DLL queue is constantly fed by all of the sessions, it too will
- fill up.
-
- Another session will then block when attempting to add a message to the
- queue. This condition can escalate until all sessions are blocked.
- Therefore, before any session sends a message, it checks to determine if
- room exists in the queue.
-
- OS/2 is a multitasking operating system, so a routine must not be
- interrupted between the time it determines there is room on the queue and
- actually adding the message to the queue. However, you don't want to
- starve other sessions, especially not those related in any way to
- DLL_CHAT.
-
- To prevent this, DLL_CHAT uses a globally accessible system semaphore and
- assigns the semaphore immediately upon entry to any collision-sensitive
- routine. Other processes trying to enter one of these routines would block
- on this flag and wait for it to free up or for a certain amount of time to
- pass. If the flag didn't change within the specified time-out period, then
- an error condition would be returned to the calling task.
-
- Finally, there is the logout routine. When the session gets a quit or exit
- command from the keyboard, it simply exits. However, as an intrinsic part
- of exiting, all DosExitList routines are called. The exit routine, in
- turn, calls the DLL's logout routine, which then sets the semaphore and
- proceeds to loop through the outstanding message list. For each
- outstanding message, it sets the flag as if the process had already
- received the message. After each flag word has been set, it is examined to
- determine if it has been read by all processes; if so, it is removed from
- the queue.
-
- Each message on the queue is a member of a linked list, and its memory is
- allocated from the global memory pool. When removing a message from the
- queue, the pointers of the other messages it points to are modified to
- point to each other, and then the memory is deallocated.
-
-
- Caveats and Warnings
-
- There are a few things you have to be aware of when you're designing your
- DLLs. I mentioned the extraordinary lengths I went to in the original
- design of DLL_CHAT to assure that certain areas of the code are
- protected against two competing tasks attempting to access them at
- once. This is a problem inherent in any multiprocessing system.
- Typically, the problem is called "reentrancy," that is, a piece of code
- being entered by a calling process before another process has finished its
- call. The actual definition of reentrancy is much stricter than this, but
- this is good enough for our purposes.
-
- Utilizing semaphores is effective in most circumstances. However, the
- method I chose would not work best without some of the special hooks OS/2
- provides for the safe use of semaphores. Consider what happens if the
- session currently executing a semaphored routine happens to be interrupted
- and then gets killed by some high-priority event after it sets the
- semaphore, but before the semaphore is released. Such an event could be
- as normal as a keystroke being entered, or, if attached to a comm port,
- the modem losing carrier, or something even more esoteric. There is no
- guarantee that it will return to where it left off, but if it doesn't
- return and finish the routine, the semaphore will forever be marked as in
- use.
-
- OS/2 does offer an alternative if you use one of the system semaphores.
- The semaphore is created with a DosOpenSem call, which returns a
- semaphore handle, similar to a file handle. By using other semaphore
- calls, a process can effectively keep reentrancy from occurring. In the
- event that a process owning the semaphore at that time, and therefore
- blocking others waiting on it, gets killed for some reason, even
- unintentionally, the system will effectively call DosCloseSem, which
- clears the semaphore if set and restores it as a system resource if there
- are no other references to it.
-
- Each individual session of DLL_CHAT uses a few resources, which I wanted
- to make sure were properly cleaned up as a session exits. I used the OS/2
- system call DosExitList to add some specific routines to each session's
- exit list, the list of routines that OS/2 executes on my behalf between
- the time the client program dies and the time it is buried. Currently,
- the exit routine simply calls the logout procedure, which in turn resets
- the systemwide flag word and the bits in each message and finally cleans
- up the message base and any outstanding semaphores.
-
- When designing a DLL, you should always think of worst-case scenarios,
- such as what would happen if this line of code were running while ten
- other processes were running those ten different lines of code. Since you
- cannot effectively control what the other processes are doing as they
- start to execute common areas of code, it is better to design the code as
- modularly as possible and be sure to semaphore around areas sensitive to
- multitasking occurring at just the wrong time──chances are that it will.
-
- Remember that you must not only program defensively against other
- processes using the DLL routines and their attendant data; if you opt to
- use OS/2 threads, you have to protect against their reentrant use of the
- DLL routines. Most of these considerations regarding DLLs can also be
- important when designing a threaded program.
-
- When considering unanticipated or asynchronous interruptions, you
- should think about signal catching and about not doing it in a DLL. If
- you're going to use the system to set a routine to catch a particular
- asynchronous event, such as program termination or Ctrl-C trapping
- performed with the DosSetSigHandler system call, doing it in the DLL can
- be dangerous. This is a general concept that can be applied toward
- program design not just in OS/2 but in any multiprocessing operating
- system.
-
- The concept of resource plays a critical role here. Who owns the resource
- of a signal catcher──an interrupt vector or another system facility in a
- multiprocessing operating system? Generally, the operating system
- itself owns the resource. When you take over such a system resource for
- your own purposes, you must remember that other processes might cause the
- resource to be used──and that you have no control over the event that
- resulted in the use of the resource.
-
- Normally, this would not be much of a problem, since the state of the
- resource can be saved before you take it over and then restored when
- you're done with it. But with at least one system facility, DosError,
- which allows a process to suspend hardware error processing, the state
- cannot be preserved for the next call or use. Thus, the state of the
- system can get pretty confused.
-
- Similarly, it is probably a good idea to avoid DosSetVect, which lets your
- exception handler be called when certain conditions, such as attempts to
- execute an illegal opcode, occur. Generally, if there is a possibility
- that some state in the operating system cannot be preserved owing to your
- taking over a system resource or facility, you should consider designing
- your code differently.
-
- If you must include such calls in your code, be sure to isolate thoroughly
- those portions of the code from other client members of the DLLs, and to
- preserve all other aspects of your process state. Be sure to terminate
- normally, too, not in a unique way, since DLLs have some special
- characteristics that are properly dealt with in automatic exit list
- processing upon client death.
-
-
- Design Considerations
-
- When designing your program to use DLLs, you have to be careful about a
- few things in your initial program design. First, access to all DLL
- routines is through a far call. So, although you can use the small memory
- models in the client section of your code and in the DLL itself, the
- external definition of the DLL routines must indicate it is a far routine.
- As such, the routine itself must also indicate in its prototype that it
- is a far routine; or else the CALL and RET statement types won't match.
-
- Remember to design your call to a DLL routine and the routine itself using
- the Pascal calling convention instead of the C calling convention. Although
- this is not a strict requirement (however, both calling sequences must
- match), it's a good idea for several reasons. First, it's required if you're
- using the routine as an IOPL gate. Also, it's more efficient, since the
- stack restore takes place only once in the called routine itself, and more
- languages support the Pascal convention, allowing your DLL to be used by
- these other languages.
-
- What about the data allocated in the DLL? That, too, must be addressed as
- far data from the client routines. Locally, within the DLL, it can be
- addressed as near or far as required.
-
- Before your client code ever executes, the initialization routine for the
- DLL will already have executed. Expecting any initialization by the main
- routine in your client code would be premature. Therefore, your DLL
- initialization code should only access data within the DLL itself, since
- the startup code may not even have allocated memory as of yet. You can
- find out a great deal when the loader calls your initialization
- routine, including the handle by which the DLL has been opened as well as
- the size of the heap in the calling EXE program. You can find more
- information on this in the OS/2 Programmer's Guide.
-
- What of the differences between global and instance data items? They can
- be confusing, since each DLL module has no easy method to determine
- whether the data object it is using is a private object or one common to
- all tasks. This can be tricky, since many programmers routinely use
- temporary pointers to objects that they place in global data space
- instead of allocating it on the stack for local use.
-
- It is important to recognize the differences between global data, such as
- items defined and allocated outside the scope of any C routine, and
- globally accessible data. In the first case, it is really local data, that
- is, data local to the client process itself and not accessible to other
- clients of the DLL. In the second case, it is accessible to all clients of
- the DLL, and that can fool you if you're not careful. You must be sure
- that globally accessible data items don't change value when you're not
- looking. Keeping items on a local stack frame is probably the safest bet.
- Items that are kept around without changing value are best placed in
- private client data space.
-
- You can easily indicate through the DATA statement in the EXPORT file
- which data segments you want to be allocated on a private per-client
- basis and which you want to be globally accessible. If a segment is marked
- as READONLY, then it is globally accessible. The Microsoft(R) C data group
- named CONST should always be marked as READONLY; this allows for only one
- copy of literal strings to be loaded for the entire DLL.
-
-
- Using Microsoft C
-
- There were some rumors floating around for a while that it was impossible
- to create DLLs with a C compiler because the stack segment (SS) did not
- equal the data segment (DS) upon entry into a DLL──clearly a run-time issue
- and not a compiler problem. Since the library has many routines that
- expect them to be equal, it appeared at first that creating DLLs in C was
- expressly forbidden, but this is not the case.
-
- There are some specific enhancements available in the Microsoft C Version
- 5.1 compiler that make writing C DLLs very easy.
-
- A new pragma, pragma data_seg, allows you to specify for any function that
- later loads its own data segment exactly which data segment to use. By
- specifying the data segment as
-
- #pragma data_seg (segment_name)
-
- you not only facilitate using DLLs, but you have more control over which
- data segment all initialized static and global data will reside in. The
- default data segment name if you don't specify one is the name used by
- DGROUP, which depends on the memory model you use.
-
- This is half of the solution of which data segment to use in the DLL. The
- other half is to specify the called function as one that uses the
- previously saved data segment with the _loadds keyword. Upon entry into a
- _loadds function, the current DS register is saved, the last one specified
- by the pragma data_seg directive is written into it, the function is
- executed, and the saved DS is restored upon exit. This is not such a new
- concept, since you've been able to use /Au as a compiler option for some
- time now, but this lets you specify some capability on a function-by-
- function basis.
-
- In order for the compiler to know, in advance, that the routine is going
- to be part of a dynamic-link library, the new keyword _export has been
- added. In particular, if the function has an I/O privilege level (IOPL)
- associated with it, then the number of words to reserve for the privilege
- level transition stack copy operation can be easily calculated at compile
- time if _export is used. In fact, if you use _export as part of your
- function definition, the number of words to reserve as indicated in the
- DEF file is ignored.
-
- When setting up the various data segments into their constituent types
- (SHARED and READONLY, for instance), you should also take a look at the
- map file produced from the link; some additional segments might be created
- that you hadn't thought about. In particular, some NULL segments are
- created for each group as _CONST and _BSS. So as not to confuse the
- linker, each member of the group should be specified within the SEGMENTS
- section of the EXPORT DEF file, and you need only mention the special
- segments, those with attributes different from the default setting of the
- DATA statement.
-
-
- Creating the Initialization Routine for Your C DLL
-
- Remember that the DLL, once passed through the linker, looks much like an
- EXE file. In fact, the same load routine used for your own client module
- loads the DLL itself. And, if you've defined an initialization routine
- within the DLL, it will be executed almost as if it were a standalone
- routine; it is called immediately after the DLL is loaded, and then only
- called upon subsequent loads of the DLL, if you specify so.
-
- You can easily tell the loader where the initialization routine is located
- by including a small assembly language routine as part of your DLL and
- linking it into the DLL. In fact, it is probably not a bad idea to have a
- module similar to the one in Figure 9 and to always give your DLL
- initialization routine the same name. The secret of the initialization
- routine is just that the only program the loader will find is the one that
- is addressed by the END START directive.
-
- The MSC compiler throws a small monkey wrench in your path. Meaning to be
- helpful, the compiler throws the _acrtused variable into each object
- module. This forces the linker to include some of the startup routines
- from the C run-time library into the eventual output of the linker, which
- the compiler thought was going to be a normal EXE file. To prevent this
- code from being loaded into your DLL, you should define the variable
- yourself, as external data:
-
- int _acrtused = 0x1234;
-
- or some number of particular meaning to you.
-
- Also, to have global data items show up in the named segment for the
- particular object module that you are linking, it should be initialized,
- declared as static, or both. Finally, you could also allocate static or
- global data using the keyword const along with the appropriate compiler
- switches.
-
- When writing your own DLL, you should use the -Gs switch on the MSC
- compiler to disable stack checking. Aside from the slight gain in
- efficiency──somewhat smaller code and one less function call per
- function──the DLL requires this since the stack segment is different for
- each client process and the size of the stack may vary on a per-client
- basis as well. In the few places where you really need stack checking, MSC
- provides you with an abundant set of pragmas and routines.
-
- MSC 5.1 includes some welcome additions to the run-time libraries
- package. These include a library that provides multithread support, a
- run-time library that is statically linked with your routines to provide
- for a DLL written almost entirely in C, and a run-time library that, in
- and of itself, is a dynamically linked library and works with EXE and
- other DLL libraries. The new DLL run-time libraries also allow you to use
- any of the functions that you've grown accustomed to.
-
- There are some subtle differences in the way certain things are handled
- in a multitasking/multiprocessing environment, but they are hidden
- from view in some of the header files. The errno variable, for example, is
- now a macro that translates into a function call. This is done because a
- table must now be used in the functions of the run-time library to enable
- a single run-time package to handle errors from multiple sources.
-
- By the look of things and how they operate, it is most likely safe to
- assume that semaphores were used throughout the library──to keep a call
- using a globally accessible variable from being clobbered by two client
- processes trying to use it simultaneously. This forces a heavy overhead in
- system calls to frequently called routines, but there is little choice in
- this matter. Remember that the libraries had to be written under a worst-
- case scenario, and you pay a penalty in speed and efficiency for the
- safety inherent in putting semaphores around the dangerous routines (see
- Figure 10).
-
- Certain idiosyncracies do exist in the C libraries, however. For example,
- the memory allocation functions alloc, malloc, and calloc follow the
- ANSI calling convention. However, the ANSI calling convention does not
- allow you to specify whether memory should be private or shared between
- different processes. Microsoft has yet to add functions to the library
- that allow this; this is the reason for the rather convoluted routines
- used in DLL_CHAT for dealing with allocating memory in a shared way.
-
-
- Conclusion
-
- With the introduction of DLLs in OS/2, a new programming environment has
- been created. Much like Windows and Presentation Manager programming, it
- has its own strict rules, but they make a great deal of sense once the
- underlying design concept and limits of the chip set and the appropriate
- portions of OS/2 are understood.
-
- You can avoid a lot of these sticky problems by piece-at-a-time
- programming: get as much of your program to work using routines in a more
- normal library using the library utilities and then move the routines out
- into a DLL. By adding the functionality and safeguards required for shared
- memory access between sessions and reentrancy problems, you can easily
- create a program that uses up less disk space, less memory space, and
- allows for interprocess communication in whatever manner you wish to
- design-not bad features for a new operating system to be written around.
-
- And once you've been through the maze of twisty little passages, it isn't
- so hard the next time to get through it rapidly and collect that treasure.
- The secret is just knowing a couple of key phrases and thinking ahead
- before you enter the maze.
-
-
- Figure 1: Once a DLL is created, use IMPLIB to create a LIB file from the
- EXPORT DEF file. The LIB file provides a dynamic-link reference
- that is built into the EXE file. The OS/2 loader then uses this
- information to resolve DLL references when the EXE file is
- executed. By providing both the DLL and LIB files, the internal
- format of DLL remains hiddenfrom the user of the DLL.
-
- Step 1: Create the DLL
- ┌───────────────────────┬──┐
- │OBJ Files │ │ ╔═════╗
- └───────────────────────┘ │ ┌──║ DLL ║
- ┌───────────────────────┐ │ ╔══════╗ │ ╚═════╝
- │Static Library │ ├─────║LINKER╟──────┤
- └───────────────────────┘ │ ╚══════╝ │ ╔═════╗
- ┌───────────────────────┐ │ └──║ MAP ║(Optional)
- │Export Definition File │ │ ╚═════╝
- └───────────────────────┴──┘
-
- Step 2: Create the LIB file
- ┌───────────────────────┐ ╔══════╗ ╔══════════╗
- │Export Definition File ├────────║IMPLIB╟─────────║ LIB FILE ║
- └───────────────────────┘ ╚══════╝ ╚══════════╝
-
- Step 3: Create an Application that Uses the DLL
- ┌───────────────────────┬─┐
- │LIB File │ │ ╔═════╗
- └───────────────────────┘ │ ┌──║ DLL ║
- ┌───────────────────────┐ │ ╔══════╗ │ ╚═════╝
- │Application OBJ Files │ ├──────║LINKER╟──────┤
- └───────────────────────┘ │ ╚══════╝ │ ╔═════╗
- ┌───────────────────────┐ │ └──║ MAP ║(Optional)
- │Static Library │ │ ╚═════╝
- └───────────────────────┴─┘
-
- Step 4: Load the EXE File and Resolve DLL References
- ┌─────┬─┐
- │ DLL │ │
- └─────┘ │ ╔═══════════╗ ╔═════════════════════════════════╗
- ├──────║OS/2 LOADER╟──────║ IN MEMORY EXECUTION OF EXE FILE ║
- ┌─────┐ │ ╚═══════════╝ ╚═════════════════════════════════╝
- │ EXE │ │
- └─────┴─┘
-
-
- Figure 2: Using IMPORT DEFINITION files is a better method for developing
- your DLL than using LIB files. There is no reason not to use
- IMPLIB for your finished DLL and its distribution. The EXPORT DEF
- file allows you to define everything that you need to.
-
- ┌──────────────────────────┬─┐
- │ OBJ Files │ │
- └──────────────────────────┘ │
- ┌──────────────────────────┐ │ ╔═════════╗ ╔═══════╗
- │ Static Library │ ├───║ LINK ╟────┬──║ DLL ║
- └──────────────────────────┘ │ ╚═════════╝ │ ╚═══════╝
- ┌──────────────────────────┐ │ │ ╔═══════╗
- │ EXPORT DEF File │ │ └──║ MAP ║(Optional)
- └──────────────────────────┴─┘ ╚═══════╝
- ┌──────────────────────────┬─┐
- │ IMPORT DEF File │ │
- └──────────────────────────┘ │
- ┌──────────────────────────┐ │ ╔═════════╗ ╔═══════╗
- │ OBJ Files │ ├───║ LINK ╟────┬──║ EXE ║
- └──────────────────────────┘ │ ╚═════════╝ │ ╚═══════╝
- ┌──────────────────────────┐ │ │ ╔═══════╗
- │ Static Library │ │ └──║ MAP ║(Optional)
- └──────────────────────────┴─┘ ╚═══════╝
-
-
- Figure 3: A Local Descriptor Table (LDT) contains a list of private
- descriptors accessible only to the local task. These descriptors
- can point to data segments, executable segements, call gates, and
- task gates. There can be at most 8192 descriptors, and each LDT
- can be, at most, 65,355 bytes long. Note that all LDT descriptors
- must reside, or be defined, in the Global Descriptor Table.
-
- GDT ╔══════════════════════════════╗
- ┌─────────────────────────────────────┐ ┌──║ LDT(1) ║
- │GLOBAL DATA SEGMENT DESCRIPTORS │ │ ║DATA SEGMENT DESCRIPTORS ║
- ├─────────────────────────────────────┤ │ ║EXECUTABLE SEGMENT DESCRIPTORS║
- │GLOBAL EXECUTABLE SEGMENT DESCRIPTORS│ │ ║CALL GATE DESCRIPTORS ║
- ├─────────────────────────────────────┤ │ ║TASK GATE DSCRIPTORS ║
- │GLOBAL CALL GATE DESCRIPTORS │ │ / : /
- ├─────────────────────────────────────┤ │ ║ : ║
- │GLOBAL TASK GATE DESCRIPTORS │ │┌─║ LDT(N) ║
- ├─────────────────────────────────────┤ ││ ║DATA SEGMENT DESCRIPTORS ║
- │GLOBAL TASK STATE SEGMENT DESCRIPTORS│ ││ ║EXECUTABEL SEGMENT DESCRIPTORS║
- ├─────────────────────────────────────┤ ││ ║CALL GATE DESCRIPTORS ║
- │(LOCAL DESCRIPTOR TABLE DESCRIPTORS) ├─┘│ ║TASK GATE DESCRIPTORS ║
- │ LDT(1) DESCRIPTOR │ │ ╚══════════════════════════════╝
- / : / │
- │ LDT(N) DESCRIPTOR ├──┘
- └─────────────────────────────────────┘
-
-
- Figure 4: DEF File Options and Parameters
-
- LIBRARY [name][init_type]
-
- NAME [appname][apptype]
-
- CODE [load][executeonly][iopl][conforming]
-
- DATA [load][readonly][instance][iopl][shared]
-
- SEGMENTS <segmentname> [CLASS 'classname']
- [load][readonlyexecuteonly][iopl][conforming][shared]
-
- EXPORTS <name>[=internalname][@ordinal][RESIDENTNAME][pwords]
-
- IMPORTS [name=]<modulename>.<entryname>
-
-
- Figure 5: Additional DEF File Options
-
- STUB 'filename' Allows you to specify the name of a DOS 3.x file to
- be run if this file is run under DOS instead of under
- OS/2.
-
- PROTMODE Indicates that this file can only be run in protected
- mode. An aid to the linker.
-
- OLD This statement allows you to preserve the names
- associated with ordinal numbers in a multi-DLL
- environment.
-
- REALMODE The opposite of PROTMODE, this indicates the program
- can only be run in real mode, and is an aid to the
- linker.
-
- EXETYPE Insures that the specified operating system is the
- current one for the program. You can specify OS2,
- WINDOWS.
-
- HEAPSIZE Determines how much local heap must be allocated within
- the automatic data segment.
-
- STACKSIZE Allows you to specify how much space should be reserved
- in the stack segment when the program is run.
-
-
- Figure 6: A Typical IMPLIB Statement
-
- IMPLIB COM_STUF.LIB COM_INP.DEF COM_OUT_DEF
-
-
- Figure 7:
-
- ───────────────────────────────────────────────────────────────────────────
- Also see:
- An overview of the way DLL_CHAT interfaces to its dynamic-link library
- ───────────────────────────────────────────────────────────────────────────
-
- DEF File Format for DLL_CHAT
-
- IMPORTS CHATLIB.login
- IMPORTS CHATLIB.get_msg_cnt
- IMPORTS CHATLIB.send_msg
- IMPORTS CHATLIB.logout
- IMPORTS CHATLIB.get_msg
-
- Header File for DLL_CHAT
-
- /* Header file for DLL_CHAT */
-
- struct gdtinfoarea{
- unsigned long time;
- unsigned long milliseconds;
- unsigned char hours;
- unsigned char minutes;
- unsigned char seconds;
- unsigned char hundreths;
- unsigned timezone;
- unsigned timer_interval;
- unsigned char day;
- unsigned char month;
- unsigned year;
- unsigned char day_of_week;
- unsigned char major_version;
- unsigned char minor_version;
- unsigned char revision_number;
- unsigned char current_screen_group;
- unsigned char max_num_of_screengrps;
- unsigned char huge_selector_shift_count;
- unsigned char protect_mode_indicator;
- unsigned foreground_process_id;
- unsigned char dynamic_variation_flag;
- unsigned char maxwait;
- unsigned minimum_timeslice;
- unsigned maximum_timeslice;
- unsigned boot_drive;
- unsigned char reserved[32];
- };
-
- struct ldtinfoarea{
- unsigned current_process_pid;
- unsigned parent_pid;
- unsigned priority_of_current_thread;
- unsigned thread_id_of_current_thread;
- unsigned screen_group;
- unsigned subscreen_group;
- unsigned current_process_is_in_fg;
- };
-
- Code Listing for DLL_CHAT
-
- /*********************************************************************
- * DLL_CHAT.C - A demonstration program using a demo DLL
- *
- * (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
- *
- * This is the main body of the CHAT program, interfacing with
- * and calling the DLL as if it were a bunch of routines
- * available with a far call: which it is!
- *
- *
- * Compile with:
- * cl /c chat.c
- * link chat,chat,,slibce+doscalls,chat
- *
- */Remember: move the DLL itself into your DLL library directory
- *********************************************************************
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "chat.h"
-
- #define TRUE 1
- #define FALSE 0
- #define OK TRUE
- #define NOT__OK FALSE
-
- #define MAX_MSG 100
- #define MAX_MSG_LEN 80
- #define NULLP (void *)NULL
-
- /* The following OS/2 system calls are made in this module: */
-
- extern far pascal dosexitlist();
- extern far pascal dossleep();
-
- /* The following DLL system calls are made in this module: */
-
- extern far _loadds pascal login();
- extern far _loadds pascal logout();
- extern far _loadds pascal get_msg_cnt();
- extern far _loadds pascal get_msg();
- extern far _loadds pascal send_msg();
-
-
- /********************************************************************
- * This is where the messages are stored, once received and
- * formatted. This could probably be replaced easily with a call
- * to calloc(), but then we wouldn't have to block on being a
- * background process
- ********************************************************************/
-
- char msg_array[MAX_MSG + 1][MAX_MSG_LEN];
-
- /* Must be global so that before_death() can access it to logout */
-
- int my_id = 0;
-
- #define MAX_SLEEP 2
-
- /********************************************************************
- * before_death()
- *
- * Called the coins are lowered onto the eyes of this invocation
- * of CHAT. Any exit will cause this routine to be called. After
- * this routine calls the DLL logout procedure, it removes the
- * next exitroutine in the exit list.
- ********************************************************************/
-
- void far before_death()
- {
- logout(my_id);
- dosexitlist(3, before_death);
- }
-
- /********************************************************************
- * main()
- *
- * After logging in (which returns a unique login id), the
- * before_death() routine is added onto the exitlist. Then the
- * main loop:
- *
- * If there are any messages, read them into out memory buffer
- * (provided there is room) and kick up the count. After
- * retrieving all the msgs which will fit, call the display
- * routine for each msg. Zero the count of messages when done.
- *
- * Every couple of seconds, sleep for a little while (as if a
- * human typing on a keyboard), then send the message to all
- * other members of CHAT.
- *
- * CHAT can only be exited (in its current form) with a
- * control-C or error condition.
- ********************************************************************/
-
- main()
- {
- int msg_cnt = 0;
- int msg_id = 0;
- int l_cnt;
- char tmp_buf[MAX_MSG_LEN];
- int mcnt;
-
- printf("Logged into CHAT as user:%d\n", my_id = login());
-
- dosexitlist(1, before_death);
-
- while (TRUE)
- {
- for (m_cnt = get_msg_cnt(my_id); m_cnt ; m_cnt-)
- {
- get_msg(my_id, (char far *)tmp_buf);
- if (msg_cnt <= MAX_MSG)
- sprintf(msg_array[msg_cnt++],"(%d)%s",my_id,tmp_buf);
- }
-
- if (ok_to_disp())
- {
- for (lp_cnt = 0 ; lp_cnt < msg_cnt; lp_cnt++)
- disp_msg(msg_array[lp_cnt]);
- if (msg_cnt > MAX_MSG)
- disp_msg("Looks like you might have lost some")
- disp_msg(" messages while you were away\n");
- msg_cnt = NULL;
- }
-
- if (rand() % (my_id + 1))
- {
- dossleep((long)(rand() % MAX_SLEEP) * 1000L);
- sprintf(tmp_buf, "Test message #%d from Session #%d\n",
- msg_id++, my_id);
- if (send_msg(my_id, (char far *)tmp_buf) == NOT_OK)
- printf("?Can't send a message....\n");
- }
- }
- }
-
- disp_msg(ptr)
- char *ptr;
- {
- printf("%s", ptr);
- fflush(stdout);
- }
-
- extern far pascal dosgetinfoseg();
-
- ok_to_disp()
- {
- struct gdtinfoarea far *gdt;
- struct ldtinfoarea far *ldt;
- unsigned gseg;
- unsigned lseg;
-
- dosgetinfoseg((char far *)&gseg, (char far *)&lseg);
- gdt = (struct gdtinfoarea far *)((long)gseg << 16);
- ldt = (struct ldtinfoarea far *)((long)lseg << 16);
-
- return( gdt->foreground_process_id == ldt->parent_pid);
- }
-
- ───────────────────────────────────────────────────────────────────────────
- An overview of the way the example program, DLL_CHAT, interfaces to its
- dynamic-link library.
- ───────────────────────────────────────────────────────────────────────────
-
- ┌──────────────────────────────────────────────────────────────────────────┐
- │ DLL_CHAT.C (First session of DLL_CHAT) │
- ├─────────────────┬───────────────────┬──────────────────┬─────────────────┤
- │ login() │ get_msg_cnt() │ get_msg() │ send_msg() │
- └───┬────────────┴────┬─────────────┴─────┬───────────┴───┬────────────┘
- │ │ my_id │ my_id │ my_id │
- │ my_id │ msg_cnt │ │ │ status
- │ │ │ │ ptr │ ptr │
- │ │ │ │ │ │ │ │
- ┌──────────┴─────┬────────────┴─────┬───────────┴─────┬───────────┴────┐
- │ login() │ get_msg_cnt() │ get_msg() │ send_msg() │
- ├─────────────────┼───────────────────┼──────────────────┼─────────────────┤
- │(returns first │(returns msg cnt │ (copies first │(allocates memory│
- │unused login │of outstanding │ outstanding │as required for │
- │slot) │messages for │ msg for this │message structure│
- │ │login id) │ id into array │and message, then│
- │ │ │ at ptr) │copies msg at ptr│
- │ │ │ │into this memory)│
- ├─────────────────┴───────────────────┴──────────────────┴─────────────────┤
- │ DLL_CHAT.LIB (Other sessions of DLL_CHAT) │
- └──────────┬──────────────────┬──────────────────┬───────────────┬─────┘
- │ │ │ │ │ │ │ │
- │ my_id my_id │ my_id │ my_id │
- │ │ │ msg_cnt │ │ │ status
- │ │ │ │ ptr │ ptr │
- ┌───┴────────────┬────┴─────────────┬─────┴───────────┬────┴───────────┐
- │ login() │ get_msg_cnt() │ get_msg() │ send_msg() │
- ├─────────────────┴───────────────────┴──────────────────┴─────────────────┤
- │ DLL_CHAT.C (Other sessions of DLL_CHAT) │
- └──────────────────────────────────────────────────────────────────────────┘
-
-
- Figure 8:
-
- DEF File for CHATLIB
-
- LIBRARY CHATLIB INITGLOBAL
-
- DATA SINGLE SHARED
-
- EXPORTS login
- EXPORTS get_msg_cnt
- EXPORTS send_msg
- EXPORTS logout
- EXPORTS get_msg
-
- Code listing for CHATLIB
-
- /**********************************************************************
- * CHATLIB.C - A demonstration Dynamic Link Library
- *
- * (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
- *
- * This DLL, when used with the CHAT program acts as a central
- * repository for all messages being passed
- *
- * Compile with:
- *
- * cl /AL /Au /Gs /c chatlib.c
- *
- * Note - Though broken here the following two lines are
- * entered as one line:
- *
- * link startup+chatlib+chatmem,chatlib.dll,,llibcdll+doscalls,
- * chatlib/NOE
- *
- * Remember to move the DLL itself into your DLL library
- * directory
- *
- ********************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- #include <dos.h>
-
- #define TRUE 1
- #define FALSE 0
- #define OK TRUE
- #define NOT_OK FALSE
-
- #define NULLP ((char *)NULL)
- #define GET_SEM (dossemrequest(&semaphore, -1L))
- #define RLS_SEM (dossemclear(&semaphore))
-
- /* The following OS/2 system calls are made in this module: */
-
- extern far pascal dossemrequest();
- extern far pascal dossemclear();
-
- /* The following external calls are made in this module: */
-
- char *my_calloc();
-
- /* This semaphore used to coordinate access to "critical" areas */
-
- long semaphore = 0;
-
- /*******************************************************************
- * This structure defines the members of the linked list
- * which starts at master_ptr. Once a structure is allocated,
- * it is never released, although the character array member
- * msg_ptr points to will be released when the message is no
- * longer needed
- ********************************************************************/
- #define MSG struct _msg
- MSG{
- MSG *next_ptr; /* Point to next MSG, or NULLM */
- char *msg_ptr; /* Point to the actual message */
- int msg_len; /* length of the message - optional */
- unsigned f_word; /* flag_word. When set to 0xfff */
- /* all chat members have seen this */
- /* message, so it can be freed */
- };
-
- int flag_word = 0xffff; /* This is the word that f_word is */
- /* set to initially. It is modified */
- /* so that each bit is "off" if that*/
- /* "user" is logged in */
-
- #define NULLM ((MSG *)NULL)
- MSG *master_ptr = NULLM; /* Where the linked list begins */
-
- /********************************************************************
- * new_msg_struct(pointer to last MSG)
- *
- * Allocates a new MSG, initializes the contents of the
- * structure, and sets the linked list if not the first time
- * called (last_msg != NULLM)
- *
- * Returns a pointer to the structure allocated, NULLM if an error
- ********************************************************************/
-
- MSG *new_msg_struct(MSG *last_msg)
- {
- MSG *tmp_ptr;
-
- if ((tmp_ptr = (MSG *)my_calloc(sizeof(MSG), 1)) == NULLM)
- return(NULLM);
-
- tmp_ptr->next_ptr = NULLM;
-
- tmp_ptr->msg_ptr = NULLP;
- tmp_ptr->msg_len = NULL;
-
- tmp_ptr->f_word = flag_word;
-
- if (last_msg != NULLM)
- last_msg->next_ptr = tmp_ptr;
-
- return(tmp_ptr);
- }
-
- /********************************************************************
- * initialize()
- *
- * Called either by the initialization routine of the DLL, or by
- * the first login. It allocates the first MSG structure, then
- * allocates and sets up for the next member
- ********************************************************************/
-
- void far _loadds pascal initialize()
- {
-
- if ((master_ptr = new_msg_struct(NULLP)) == NULLM)
- {
- printf("Couldn't allocate MSG memory for header...\n");
- exit(1);
- }
-
- new_msg_struct(master_ptr);
- }
-
- /********************************************************************
- * login()
- *
- * If the master MSG structure hasn't been allocated already by
- * an earlier call to initialize() (by the DLL initialize
- * routine), then make the call now. Memory has already been
- * allocated, therefore, so now give ourselves access to the
- * segment we've allocated.
- *
- * Get the next free bit slot in the flag word, set it to
- * indicate it's in use, then return our login id.
- ********************************************************************/
-
- int far _loadds pascal login()
- {
- int log_id;
- int tmp_msk;
-
- if (master_ptr == NULLM)
- {
- printf("Init in login\n");
- initialize();
- }
-
- my_getseg();
-
- GET_SEM;
- for (log_id= 0 ; log_id < 16 ; log_id++)
- {
- tmp_msk = mask(log_id);
- if (flag_word & tmp_msk)
- {
- flag_word &= ~tmp_msk;
- RLS_SEM;
- return(log_id);
- }
-
- }
- RLS_SEM;
-
- printf("Login slots all used up!\n");
- exit(1);
- }
-
- /********************************************************************
- * get_msg_cnt(login_id)
- *
- * For every MSG structure in the linked list with an associated
- * message attached to it, increment a counter if the id in
- * question hasn't received it yet, then return that counter
- * when we fall off the end.
- ********************************************************************/
-
- int far _loadds pascal get_msg_cnt(int id)
- {
- MSG *tmp_ptr;
- int tmp_cnt = 0;
- int tmp_msk = mask(id);
-
- GET_SEM;
- for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
- {
- if (!(tmp_ptr->f_word & tmp_msk))
- if (tmp_ptr->msg_len)
- tmp_cnt++;
- }
-
- RLS_SEM;
- return(tmp_cnt);
- }
-
- /********************************************************************
- * send_msg(login_id, pointer_to_message)
- *
- * If there are no other "chatter's" logged in, simply return.
- * (Flag_word or'ed with our mask would be 0xfff)
- *
- * Find a free MSG structure (guaranteed to have at least one,
- * since every write leaves a free one allocated if its the last
- * one in the linked list).
- *
- * Allocate memory for the message, copy the message into it,
- * then assign the pointer in the structure and the length of
- * the message. Finally, allocate a new structure if required.
- ********************************************************************/
-
- int far _loadds pascal send_msg(int id, char far *ptr)
- {
- MSG *tmp_ptr = master_ptr;
- int tmp_len = strlen(ptr) + 1;
-
- if ((flag_word | mask(id)) == 0xffff)
- return(OK);
-
- GET_SEM;
- while (tmp_ptr->msg_len)
- tmp_ptr = tmp_ptr->next_ptr;
-
- if ((tmp_ptr->msg_ptr = my_calloc(tmp_len, 1)) == NULLP)
- {
- printf("Can't allocate %d bytes for msg\n", tmp_len);
- RLS_SEM;
- return(NOT_OK);
- }
-
- strcpy(tmp_ptr->msg_ptr, ptr);
- tmp_ptr->msg_len = tmp_len;
- tmp_ptr->f_word = (flag_word | mask(id));
-
- if (tmp_ptr->next_ptr == NULLM)
- {
- if (new_msg_struct(tmp_ptr) == NULLM)
- {
- printf("Can't allocate new MSG_header\n");
- free_msg(tmp_ptr);
- RLS_SEM;
- return(NOT_OK);
- }
- }
-
- RLS_SEM;
- return(OK);
- }
-
- /********************************************************************
- * logout(login_id)
- *
- * Mark every mesage as read (freeing them if now "totally"
- * read),reset the flag word, and then indicate that the logout
- * worked.
- ********************************************************************/
-
- int far _loadds pascal logout(int id)
- {
- MSG *tmp_ptr;
- int tmp_msk = mask(id);
-
- GET_SEM;
- for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
- mark_msg(id, tmp_ptr);
-
- flag_word |= mask(id);
-
- RLS_SEM;
-
- printf("In logout ... Hit a Key:");fflush(stdout);
- getch();
- printf("\n\n\n\n");
-
- return(0);
- }
-
- /********************************************************************
- * get_msg(login_id, pointer to buffer)
- *
- * Find the first message the login_id hasn't read, then strcpy
- * it into the buffer supplied. Then mark the message as read
- * (freeing as required).
- ********************************************************************/
-
- int far _loadds pascal get_msg(int id, char far *ptr)
- {
- MSG *tmp_ptr = master_ptr;
- int tmp_msk = mask(id);
-
-
- GET_SEM;
- for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
- {
- if (!(tmp_ptr->f_word & tmp_msk))
- {
- strcpy(ptr, tmp_ptr->msg_ptr);
- mark_msg(id, tmp_ptr);
- RLS_SEM;
- return(TRUE);
- }
- }
- RLS_SEM;
- return(FALSE);
- }
-
- /********************************************************************
- * mark_msg(login id, pointer to message structure)
- *
- * Mark our bit in the MSG f_word as set. If then set to
- * 0xffff, the message is "totally" read, so free it.
- *
- *******************************************************************
- *
- * free(pointer to message structure)
- *
- * If there is a string associated with this structure, free the
- * memory so used, then zero out the pointer and the msg_len
- ********************************************************************/
-
- mark_msg(int id, MSG *ptr)
- {
- ptr->f_word |= mask(id);
- if (ptr->f_word == 0xffff)
- free_msg(ptr);
- }
-
- free_msg(MSG *ptr)
- {
- if (ptr->msg_ptr)
- my_free(ptr->msg_ptr);
- ptr->msg_ptr = NULLP;
- ptr->msg_len = NULL;
- }
-
- /* GENERAL ROUTINES */
-
- /* This routine merely returns with the bit corresponding
- * to our login set
- */
-
- mask(int log_id)
- {
- return(1 << (log_id - 1));
- }
-
- Additional Module for CHATLIB
-
- /* CHATMEM.C - Memory allocation routines for shared DLL memory
- *
- * (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
- *
- * This module contains three functions. Allocation of memory,
- * de-allocation of memory and the getseg call.
- *
- * The current ANSI calloc/alloc/malloc sequence does not allow
- * for an additional parameter to specify if memory requested
- * through these functions is to be sharable or private.
- * Therefore the MSC library calls all allocate private memory.
- *
- * These routines allocate a 64K chunk of memory, requested from
- * OS/2 as a sharable chunk, then dole it out using the DosSub
- * allocation calls.
- *
- * Only one 64K chunk is allocated: if more memory is desired an
- * additional call to dosallocseg would have to be made and all
- * sessions already logged in given access to the chunk. Out of
- * laziness, that was not done for these demonstration
- * routines.
- *
- *
- * Compile with:
- *
- * cl /AL /Au /Gs /c chatmem.c
- *
- * Note - Though broken here, the following two lines are
- * entered as one line:
- *
- * link startup+chatlib+chatmem,chatlib.dll,,llibcdll+doscalls,
- * chatlib/NOE
- *
- * Remember to move the DLL itself into your DLL library
- * directory
- ********************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- #include <dos.h>
-
- #define TRUE 1
- #define FALSE 0
-
- /* The following OS/2 system calls are made in this module: */
-
- extern far pascal dosallocseg();
- extern far pascal dosfreeseg();
- extern far pascal dossuballoc();
- extern far pascal dossubset();
- extern far pascal dossubfree();
- extern far pascal dosgetseg();
- extern far pascal dossemrequest();
- extern far pascal dossemclear();
-
- #define NULLP (char *)NULL
-
- /* This semaphore is so that we don't hurt ourselves as we
- * allocate and deallocate memory
- */
-
- long memory_semaphore = NULL;
-
- /* This is the actual selector which the 64K dosallocseg()
- * call returns
- */
-
- unsigned major_selector = NULL;
-
- /********************************************************************
- * my_calloc(number_of_items,size_of_item)
- *
- * Emulates the more typical calloc call, but returns memory
- * which can later be allocated as sharable.
- *
- * After the first call (which causes a 64K chunk to be
- * allocated), all subsequent calls cause a dossuballoc call to
- * be made, and a long pointer to the returned memory to be
- * created and returned
- *
- * The 64K chunk must be initialized by the dossubset call
- * before it can be used by other dossub functions.
- *
- * Because the dossubfree call requires a size, the size
- * requested plus the sizeof an int is actually allocated and
- * the size of the total request is then stored in the first two
- * bytes of the returned character array. The ptr returned,
- * however, is this memory location plus the initial sizeof and
- * int-therefore the bookkeeping is transparent to the
- * application task.
- ********************************************************************/
-
- char *
- my_calloc(size1, size2)
- int size1;
- int size2;
- {
- unsigned long selector;
- int stat;
- char *ptr;
- int sizeit = (size1 * size2) + sizeof(int);
-
- dossemrequest(&memory_semaphore, -1L);
- if (!major_selector)
- {
- if (stat = dosallocseg(0, &major_selector, 3))
- {
- printf("dosalloc error:%d\n", stat);
- dossemclear(&memory_semaphore);
- return(NULLP);
- }
-
- if (stat = dossubset(major_selector, 1, 0))
- {
- printf("Error in dossubset:%d\n", stat);
- dossemclear(&memory_semaphore);
- return(NULLP);
- }
- }
-
- selector = 0;
- if (stat = dossuballoc(major_selector, &selector, sizeit))
- {
- printf("dossuballoc error:%d\n", stat);
- dossemclear(&memory_semaphore);
- return(NULLP);
- }
-
- dossemclear(&memory_semaphore);
- ptr = (char *)(((long)major_selector << 16) + (long)selector);
- memset(ptr, (char)NULL, sizeit);
- *(int *)ptr = sizeit;
- return(ptr + sizeof(int));
- }
-
- /********************************************************************
- * my_free(pointer_to_a_character_array_previously_my_calloc'ed)
- *
- * Subtract sizeof an int from the pointer, dereference as an
- * int, then free that number of bytes.
- *
- ********************************************************************/
-
- my_free(ptr)
- char *ptr;
- {
- int stat;
-
- ptr -= sizeof(int);
-
- dossemrequest(&memory_semaphore, -1L);
- if (stat = dossubfree(major_selector, FP_OFF(ptr), *(int *)ptr))
- {
- printf("Error freeing: %lx\n", ptr);
- exit(1);
- }
- dossemclear(&memory_semaphore);
- }
-
- /********************************************************************
- * my_getseg()
- *
- * Causes the memory affilaited with the major_selector to
- * become accessible to this process.
- ********************************************************************/
-
- my_getseg()
- {
- int stat;
-
- if (stat=dosgetseg(major_selector))
- printf("Error on getseg:%d\n", stat);
- return(stat);
- }
-
- }
-
-
- Figure 9: A DLL Initialization Routine
-
- EXTRN INITROUTINE:FAR
- ASSUME CS:_TEXT
- _TEXT SEGMENT BYTE PUBLIC 'CODE'
- START PROC FAR
- call INITROUTINE ; the real initialization routine
- ret
- START ENDP
- _TEXT ENDS
- END START ; defines auto-init entry point
-
-
- Figure 10: An Example of Semaphored Printf() vs. "Free-style" Printf()
-
- Consider the two code fragments below. In the semaphore version of the
- code, individual printf()'s are not interrupted──whatever they are printing
- will be printed to completion with other calls to the same printf()
- routine waiting on a semaphore before proceeding. This semaphore is reset
- when the current process using the library function is exiting from the
- library function.
-
- So-called "free-style" functions, on the other hand, can be interrupted
- at any time-even in the middle of printing. Therefore, you might have two
- (or more!) streams of data merged into some unreadable mess.
-
- It's worse, however, if global data is in use by the interrupted free-
- style function. Such data can be modified by the "interrupter," and could
- cause unpredictable results.
-
- Program 1 Program 2
-
- main() main()
- { {
- printf("Task1:%d\n", cnt++); printf("Task2:%d\n", cnt++);
- } }
-
- Semaphored Free Style
-
- Task1:1 Task2:1
- Task2:1 Task1:1
- Task1:2 Task1:2
- Task1:3 Task1:3
- Task1:4 Task2:2
- Task2:2 Task2:3
- Task2:3 Task2:4
- Task1:4 Task1:4
- ∙ ∙
- ∙ ∙
- ∙ ∙
- What would the apparent results be if two sessions, each normally
- independent of the other, but sharing a Dynamic-Link Library version of
- printf(), were to try to execute the printf() routine in a semaphored
- versus a free-style approach? The two program fragments above, each
- running as a separate session, show the differences. In the semaphore
- version, each call made to printf() by each session is run to completion,
- although the "set" might be interrupted. In the free-style version, there
- is not even a guarantee that any given printf() will finish before it is
- interrupted. The same would also hold true for multithreads of the same
- session using a common printf() routine. The motto? "If in doubt, use
- semaphores."
- ████████████████████████████████████████████████████████████████████████████
-
- New Compiler Technology Boosts Microsoft QuickBASIC 4.0 Productivity
-
- Augie Hansen
-
- Compiler technology takes a quantum leap forward with the most recent
- version of Microsoft(R) QuickBASIC. If you already have Microsoft QuickBASIC
- Version 3.0, you received an invitation to upgrade to Version 4.0 for a
- nominal charge, but don't let the word "upgrade" fool you. The Microsoft
- QuickBASIC 4.0 compiler is a brand new product. The compiler, as in
- earlier versions, comes in two flavors: a traditional command-line
- oriented compiler, BC, and a speedy in-memory integrated environment, QB,
- that truly deserves the label "instant environment."
-
- Microsoft QuickBASIC 4.0 is based on threaded pseudocode technology that
- has been under development at Microsoft for a number of years. The p-code
- technology, as it is called, draws upon the threaded-code interpreter
- environments used by FORTH programming products for many years, but with
- some important extensions and improvements.
-
- This article will take a close look at threaded p-code and the way it is
- applied to Microsoft QuickBASIC 4.0. We'll show you how the tightly
- coupled editor, p-code interpreter, and debugger all work together to
- provide instant BASIC programming that will boost your productivity.
- The development of a video terminal emulation program provides an
- example of the program in action.
-
-
- Features/Enhancements
-
- The p-code technology used in Microsoft QuickBASIC 4.0 offers a
- programming environment with startling advantages over earlier
- integrated environments. When you type a program line and press Enter
- or the down arrow key, the line is immediately compiled to intermediate
- p-code. At the same time, the line is formatted by the editor so that all
- BASIC keywords are displayed in uppercase, and all operators are
- surrounded by spaces.
-
- The line is also checked in real time for syntax errors. If any are found,
- they are reported in a dialog box; do not ignore such errors. The system
- lets you move off the line after it has told you about the problem, but be
- sure the line is syntactically correct before moving on to some other task.
-
- The net result is that a program is always ready to run virtually
- instantly. You can select Start in the Run menu (or press Shift-F5) at any
- time to see the results of your programming labors. The first time you
- start a program, it is translated from editable intermediate p-code to
- executable threaded p-code, which is as close to unoptimized native
- machine code as you can get without actually being there. This is as far
- as the code can be taken so that it can still be returned to the ASCII
- representation for editing.
-
- You can stop program execution at any time, change the values of
- variables, add or delete any number of lines, and then return to running
- the program in the blink of an eye. The trick is that Microsoft
- QuickBASIC 4.0 works on one small unit of code at a time; it decompiles
- only what you are going to be working on. If, for example, you are
- working on changes to a SUB (subprogram), only that SUB is returned to
- an editable intermediate p-code state. When you finish your changes, you
- simply select Run-Continue (or press F5) to continue executing where you
- left off before editing. Since all variable values are preserved, your
- program resumes execution in exactly the same state it was in before you
- interrupted it.
-
- Built-in debugging, as well as the syntax checking already mentioned, is
- patterned after Microsoft CodeView(R). Within the Microsoft QuickBASIC 4.0
- instant environment, you can set watch expressions, watchpoints, and
- breakpoints, single-step program execution, and even backtrack through
- previously executed statements.
-
- The separate command-line version of the Microsoft QuickBASIC 4.0
- compiler, the BC.EXE program, provides three important benefits. To begin
- with, it lets you produce disk-based executable programs in either
- standalone form or smaller executable versions that work with a single
- run-time module. Second, it provides a debugging code option that lets you
- use CodeView for source level debugging of your programs with the best
- available full-screen debugger. Finally, it makes mixed-language
- programming possible, allowing you to use existing routines written in
- Microsoft C (Version 5.0 and QuickC(TM)), Pascal, FORTRAN, and the Macro
- Assembler (MASM).
-
- Recursion has been added to this version of Microsoft QuickBASIC. Although
- recursion offers no substantial code size or speed advantages over
- equivalent iterative programming methods, some types of problems, such
- as the Quick Sort procedure and DOS-directory traversal, lend themselves
- conceptually to recursive solutions.
-
- Another valuable addition to BASIC is the user-definable data type, which
- lets you define structured data types that are equivalent to C structures
- and Pascal records. Records are important in the creation of composite
- data types which simplify organizing and tracking data and ease the
- burden of dealing with random-access files.
-
- Microsoft QuickBASIC 4.0 supports only IEEE number formats. The Microsoft
- binary format used in earlier versions of the compiler has been dropped,
- but functions have been added in order to permit data stored in the
- Microsoft binary format to be translated to the IEEE format. Also,
- automatic detection and use of a numeric data processor (NDP), a math
- coprocessor, has been added. Executable programs can run on any IBM-
- compatible machine regardless of whether it has an NDP. If one is present,
- it is used to obtain the greatest execution speed; if not, math operations
- are emulated in software. There is no longer any need to have two
- separate versions of programs for different hardware configurations.
-
- The help system now has three levels. One level is visible on the status
- line at the bottom of the screen whenever a menu or command selection is
- visible at the top. You can also get a series of general help panels by
- pressing F1 or pressing Alt-H and choosing the General... option.
-
- Best of all, a new context-sensitive help feature gives you on-line access
- to BASIC manual pages for all statements, functions, and other keywords.
- You simply move the editing cursor into the item of interest, and press
- Shift-F1 or select the Topic option from the Help menu. The view window
- shrinks as much as necessary to display the help frame above your source
- code (see Figure 1). The editor is still active while the help
- information is on the screen, letting you continue to edit while looking
- at the syntax of a statement. After you have viewed the help window, press
- Esc to close it.
-
- For those of you who are beginning to program under an OS/2 system,
- Microsoft QuickBASIC 4.0 will be provided as a real-mode option under the
- BASIC compiler Version 6.0. The BASIC compiler 6.0, which is completely
- compatible with Microsoft QuickBASIC 4.0, is essentially what Microsoft
- will provide for OS/2 BASIC programming. All OS/2 system calls will be
- supported directly from BASIC in the OS/2 version of the compiler.
-
-
- Program Development
-
- The Microsoft QuickBASIC development cycle is shown in Figure 2. The
- figure compares the Version 3.0 development cycle with the Version 4.0
- cycle. As you can see, the compile step appears to be missing from the 4.0
- cycle, at least from your perspective as a developer.
-
- The compile step really is there, of course, but it happens so fast that
- it's not usually visible to you. When you first read in a new module's
- source from disk, you only see a compile delay as the statements are
- parsed and compiled, and even that process is fast. Creating a new program
- or editing a program that is already in memory produces an interpreter-
- like operation.
-
- Transitions among the three steps are bidirectional. You can switch from
- the "run" mode to the "edit" mode and back easily and quickly, or between
- run and "debug", or edit and debug. The seamless integration of the edit,
- run, and debug steps into an instant environment is something to behold.
-
- Microsoft QuickBASIC 4.0 is heavily geared toward modular programming. The
- recommended units of modularity are procedures called subprograms (SUBs)
- and functions (FUNCTIONs). FUNCTIONs are new to the environment and
- should not be confused with the DEF FNs of older BASICs. Subprograms
- have many of the attributes of BASIC subroutines, but you don't have to
- GOSUB much anymore. Indeed, there are many reasons favoring the use of
- subroutines instead of traditional BASIC subprograms.
-
- SUBs accept parameters and provide for local storage. A variable defined
- inside a subprogram is not visible outside that subprogram unless you go
- out of your way to make it visible by sharing it. You can pass values into
- a SUB via parameters that use the call-by-reference method, which makes
- the address of a parameter available to the SUB so that the statements
- in the SUB can affect the parameter itself.
-
- If you want call-by-value parameter passing, which means that a SUB can
- use the value passed as a parameter but cannot access the original
- storage location, you can simulate it by assigning parameters into
- intermediate variables and passing the intermediates. You can also
- surround the expression or variable identifier in parentheses to force
- call-by-value parameter passing.
-
- Functions are similar to subprograms, except that they return a value by
- assigning the value to the function name. You can use functions in expres-
- sions just as you use BASIC intrinsic functions, such as the INPUT$
- function.
-
- You can declare variables local to either subprograms or functions as
- STATIC so that they retain their values from one call to the next.
- Alternatively, you can declare them without the STATIC keyword, in which
- case all local variables are dynamic. They are managed on the stack and
- exist only while the procedure is executing.
-
- When a program is in memory, it typically has code at two levels: module
- level and procedure level. Module-level code is at the top level. Items
- declared and defined at module level are global and are available
- throughout the module. Procedure-level code is contained within
- procedures and has local scope by default. Statements within procedures
- can access module-level items if those items are declared in DIM or COMMON
- statements. Procedures can also declare SHARED access to module-level
- items.
-
- Quick Libraries are another important tool for writing modular programs.
- You can create procedures in any Microsoft language, compile them to OBJ
- files, and collect them into Quick Libraries (QLB extension) that can
- then be loaded into the Microsoft QuickBASIC environment. The procedures
- in loaded Quick Libraries are immediately available for use in the active
- program. You can also ship libraries to other Microsoft QuickBASIC 4.0
- users to incorporate into their programs.
-
- You can create Quick Libraries and standard standalone libraries (LIB
- extension) either from the Microsoft QuickBASIC instant environment or
- from the command line. However, the one-step process of the instant
- environment makes creating libraries straightforward and simple.
-
-
- Threaded P-Code
-
- As we have already seen, Microsoft QuickBASIC 4.0 is both a compiler and
- an interpreter. The compiler keeps your program in a pseudocode state
- that is about 90 percent machine code. This p-code retains enough
- information to allow reconstruction of your original source while having
- only one internal representation of the program in memory at a time.
-
- This is crucial because the program is always ready to be run, debugged,
- and edited without a significant amount of translation time, and almost
- no execution speed penalty. And because of the single representation of
- your program instead of separate source, object, and executable
- representations in memory, there is more room for your program.
-
- The threaded p-code interpreter is not a discrete program entity, but
- rather a distributed interpreter. When you key in a BASIC source line,
- Microsoft QuickBASIC immediately checks its syntax. If errors are found,
- you are obliged to remove them before proceeding. If the syntax is good,
- the source line is translated into a threaded p-code representation that
- consists of a series of executor addresses, each of which is simply the
- memory address of an executor.
-
- An executor is a routine that participates in doing the work of a BASIC
- statement. A single BASIC statement usually results in a series of
- executors being chained together, or threaded. The threading is achieved
- by the distributed interpreter, which consists of two machine
- instructions. The cost is four bytes and seven CPU cycles per executor.
- Each executor looks like this:
-
- Executor routine + LODSW ES + JMP AX
-
- That's it. Your program is executed simply by chaining together all the
- executors that are needed to carry out its tasks. There is no wasteful
- CALL/RET instruction overhead, so your program runs quickly-about as fast
- as unoptimized machine code will run.
-
- Figure 3 shows how Microsoft QuickBASIC handles your program. The
- environment has three states called "parsed," "symbolic," and "threaded."
- A threaded p-code system with only the parsed and threaded states would
- appear slow because the time needed to translate from parsed code to
- threaded p-code accumulates. However, by introducing the intermediate
- symbolic state, Microsoft has achieved the goal of instant program
- changes.
-
- The parsed state is the result of parsing your BASIC statements, either
- from keyboard input or from disk files. Most of the syntax checking is
- done during this translation. Microsoft QuickBASIC 4.0 has the portions
- of your program that are affected by editing changes to COMMON, SHARED, or
- DEF statements in this state while the changes are made. Translation back
- to the threaded state occurs at a rate of about 60,000 lines per minute
- on an 8-MHz IBM(R) PC/AT(R).
-
- The symbolic state is distinguished by the efficient symbol table
- information that permits fast data access. Most editing that does not
- involve global impacts is done in this state. Only the portions of your
- program that are affected by editing need to be brought to this state.
-
- Your program is run and debugged in the threaded state; it is in the
- threaded state most of the time. Translation from symbolic to threaded
- state involves inserting type checking code, binding procedure calls,
- control structures, labels to memory addresses, linking COMMON data, and
- generating the p-code executor addresses.
-
- When you make nonglobal changes to your program, only the affected
- portions are returned to the symbolic state. Before the program can run
- again, the edited portions are translated back to threaded state. The
- round trip from threaded to symbolic and back to threaded occurs at about
- 150,000 lines per minute on a typical 8MHz AT machine. Because you usually
- deal with a single program item at a time, such as a FUNCTION or SUB, this
- translation is virtually instantaneous.
-
-
- Debugging Made Easy
-
- The tight coupling between the run step and the edit step (see Figure 1)
- is also true of the relationship of the run and debug steps. The built-
- in debugging feature is a substantial subset of the Microsoft CodeView
- full-screen symbolic debugger. It lets you observe any variable or set of
- variables in your program, single-step execution, set breakpoints, trace
- the stack, and get a statement execution history. If you detect a run-
- time error condition, you can use the editor to make needed changes
- quickly and return immediately to the run step. SET NEXT STATEMENT is
- also available, and allows the programmer to set the next statement to be
- executed.
-
- You can set a watchpoint, which is an expression that causes program
- execution to stop when it becomes TRUE. The debugger opens a "watch
- window" just above the view window to your source code and displays all
- watch information in it. To observe the value of a variable or an
- expression as you run your program, you can set watch expressions and
- breakpoints. The debugger displays the values of watch expressions in
- the watch window, stopping at each breakpoint so you can see the values
- before continuing execution.
-
- You can also single-step program execution (F8) to track values as each
- statement is executed. In this mode, all statements in SUBs and
- FUNCTIONs are single-stepped. You can also do procedure stepping (F10),
- which treats each SUB and FUNCTION as if it were a single statement. In
- addition, you can turn on Trace mode, which will do a slow-motion single-
- step through your program, making flow analysis and diagnosis a breeze.
-
- A history feature, enabled by either Trace On or a separate History On
- activation, allows you to record up to the last 20 lines executed in your
- program. You can then scroll backward and forward through the lines to
- examine program execution. This is particularly useful when you need to
- follow branching patterns of IF and SELECT CASE statements and to observe
- looping statements.
-
- If the built-in debugging features aren't enough for your needs, you can
- also create EXE files with debugging code that let you use CodeView for
- greater access to the most intimate details of your program. CodeView is
- not supplied with Microsoft QuickBASIC, but it comes with MASM and other
- Microsoft language products. To debug pure BASIC code, the built-in
- debugger is probably more powerful than CodeView. However, CodeView
- compatibility is invaluable for mixed-language programming.
-
-
- Smart Editor
-
- The editing capabilities of Microsoft QuickBASIC 4.0 are quite impressive.
- Syntax-directed editing, "pretty printing" of source code lines, and
- WordStar(R) editing-command compatibility are among the editor's many
- features.
-
- Microsoft QuickBASIC breaks a BASIC program module into editable and
- compilable units, such as SUBs and FUNCTIONs. As you create or edit, the
- editor keeps track of these units and displays information about them on
- demand in a program item display window. You select the program item you
- want to edit by moving a highlight and choosing one of several
- commands.
-
- The editor supports split-screen editing so that you can edit one item
- while viewing another. It also allows you to switch to an immediate window
- to execute BASIC statements in what is the equivalent of direct mode in
- earlier versions of the BASIC interpreter. The split screen capability
- also works when debugging, which effectively allows you to view both the
- calling code and the called code simultaneously.
-
- You can use the editor as a standard text editor for ASCII files if you
- wish. You simply turn off the BASIC Syntax Checking feature in the Edit
- menu in order to disable the sensitivity to BASIC language statement
- syntax.
-
-
- Command-line Compiler
-
- The Microsoft QuickBASIC instant environment is great when learning to
- program in BASIC, experimenting, and doing modest program development
- tasks. Should you get involved in large program development projects,
- perhaps working with other programmers, or if you need to do mixed-
- language programming using C, Pascal, FORTRAN, or MASM in addition to
- BASIC, you will probably need to use the command-line compiler.
-
- The BC program, in concert with MAKE, LINK, and LIB, provides the
- traditional compiler you need to handle large projects. Figure 4 shows
- how the programs work together to produce executable programs and libraries.
- Note that you can even use the QB instant environment as a front-end
- development tool for the BC compiler. When you ask QB to produce an EXE
- file, it calls on BC and the linker, LINK, to translate your program source
- into optimized machine instructions.
-
- Mixed-language programming involves the use of Microsoft-format object
- libraries that can be linked into an executable program. Both the QB and
- BC programs let you create object library (LIB) files. The LINK program
- supplied with Microsoft QuickBASIC can link object modules produced by
- the C, Pascal, FORTRAN, and BASIC compilers, as well as by MASM.
-
- Figure 5 shows the way an executable program might be created by
- combining Microsoft QuickBASIC 4.0, QuickC or C 5.0, and MASM 5.0 object
- modules. The latest CodeView debugger can debug such a mixed-language
- program at the appropriate source level as long as you specify that
- debugging code be included at each compilation and assembly step. You
- can also create Quick Libraries to be used by Microsoft QuickBASIC.
-
-
- User-defined Types
-
- Microsoft QuickBASIC 4.0 adds user-defined types to its growing list of
- modern programming features. This is something that BASIC has needed for
- a long time. A user-defined type is a data type that contains more than
- one element. C has structures and unions, and Pascal has records; now
- Microsoft QuickBASIC has TYPEs.
-
- User-defined types let you model program data to fit real-world
- conditions. Instead of having lots of separate variables that appear to
- have no relationship, you can now define data types that clearly show
- data relationships. Each element in a defined type must have a type name
- of INTEGER, LONG, SINGLE, DOUBLE, STRING (with a length specifier), or
- another user-defined type.
-
- For example, you can easily mirror the structure of an inventory-control
- card. Use the keyword TYPE to define the structure of the data, as shown
- in Figure 6. Having defined the type, you can declare variables of this
- type by using the DIM, REDIM, COMMON, STATIC, and SHARED statements.
-
- To reserve storage for a set of 100 cards, you declare an array of items
- of this type:
-
- DIM CardTable(100) AS_ CardType
-
- You must define all user types at the module level. However, you can
- declare variables of the defined type at both the module level and the
- procedure level. Within a defined type, each string must have a fixed
- length, which is specified on a string-by-string basis by the asterisk
- number modifier. Thus, PartNumber AS STRING * 20 allocates a 20-character
- string to the part number. In programs, you can use the new functions
- LTRIM$ and RTRIM$ to remove leading and trailing blanks, respectively, in
- fixed-length strings.
-
- The Microsoft QuickBASIC documentation favors the use of the term "record"
- to describe a variable of user-defined type. To access a record element,
- use the period operator. The following statement assigns a value of 20 to
- the StockQty element of the tenth card in CardTable:
-
- CardTable(10).StockQty_ = 20
-
- To make things even more interesting, you can use defined data types in
- the definition of elements of yet other user-defined types. The Supplier
- AS SupplyType element, for example, indicates that a variable of the
- user-defined type SupplyType is part of the user-defined CardType
- definition.
-
-
- A Video Terminal Emulator
-
- The best way to see how Microsoft QuickBASIC 4.0 works is to try it. The
- program shown in Figure 7 and called ADM3A.BAS is a simple video terminal
- emulator. It is a vehicle for showing off several of the new features of
- Microsoft QuickBASIC and demonstrating the user interface and is a useful
- program in its own right. ADM3A.BAS makes an IBM-compatible PC act like a
- simple video terminal, the Lear Siegler adm3a.
-
- I wrote this terminal emulation because the adm3a terminal is recognized
- by most UNIX(R) and XENIX(R) systems as well as many other multiuser systems.
- The emulator allows me access to several visually oriented programs, from
- the VI editor to custom-designed database programs.
-
- Figure 8 shows the Microsoft QuickBASIC Run menu. To run the emulator from
- the Microsoft QuickBASIC environment, you would execute the Start command
- by highlighting it on the menu or by simply pressing the keyboard shortcut
- command, Shift-F5, while in the editing mode. Figure 8 shows the
- selection of the Make EXE File... command, which produces an executable
- program that you can run at the DOS command level. All Microsoft
- QuickBASIC commands are available in menus like this, and all frequently
- used commands have shortcut keyboard aliases.
-
- The ADM3A program consists of a single module with a half-dozen
- subprograms. The module-level code declares SUBs to let the compiler check
- both the number of parameters to be passed to each SUB and their types.
- Figure 9 shows the program item list that is maintained by Microsoft
- QuickBASIC 4.0. The list keeps track of modules and procedures within
- modules. You use this work screen to select, move, and delete modules and
- procedures. The smart editor normally allows you to view and edit one
- program item at a time. However, you can split the window into two parts
- with the View menu to see different parts of a program at the same time.
-
- ts
- The module-level code defines a set of symbolic constants for logical
- values, colors, and cursor control. When you read a BASIC program, seeing
- a cold, hard number like 3 gives you virtually no information about what
- the value means. The context may provide some help, but
-
- COLOR 3, 1
-
- is still a bit mysterious if you don't know what the numbers 3 and 1 stand
- for.
-
- Using symbolic names for constant values is a better practice. Thus, the
- definition
-
- CONST BLACK = 0, _BLUE = 1, ..., CYAN = 3
-
- lets you write a statement like
-
- COLOR CYAN, BLUE
-
- that provides the information a reader needs at a glance.
-
- Notice the definition of a "record" data type, WinType. The "window type"
- relates variables for the top, bottom, left, and right of a screen
- window. The variables CmdWin and ViewWin are declared to be WinType
- record type by the dimension statements.
-
- The assignment statements following the variable declarations set up two
- window areas on the ADM3A program screen. The top screen row becomes the
- command window, and the rest of the screen is the view window. This
- arrangement works well because the physical screen of an adm3a terminal
- has 24 screen rows and is 80 columns wide. Putting the command line at
- the top of the screen keeps it from being a distraction as the user's eyes
- move from screen to keyboard and back. The InitScreen procedure takes care
- of partitioning the screen into the command window and the view window
- portions. The VIEW PRINT statement causes scrolling to occur within the
- view window when print statements attempt to write past the last line.
- The command window is stationary in this program.
-
- Communications setup is easy in BASIC. The OPEN COM statement takes a
- string parameter that specifies the serial port (COM1: or COM2:), the
- transmission speed, and settings for parity and number of data bits and
- stop bits. It opens the communication channel, if the necessary hardware
- is installed, and sets up transmit and receive buffers and an interface
- to your program.
-
- The ADM3A program uses an optional DOS environment variable to customize
- its setup. If the variable COMPARMS is defined in the DOS environment,
- its value is used as the OPEN COM string argument. Otherwise, ADM3A uses a
- set of built-in values. It then sets the values of the communication port
- address and the BREAK signal mask, which are used in the BreakSignal
- procedure.
-
- The main program loop alternately monitors the keyboard and the incoming
- communication line buffer for input. From the perspective of the local
- system, the program takes input from the keyboard and sends it to the
- remote system over the communication line. It takes incoming data from the
- communication line and puts it on the screen. This basic pattern is
- altered by certain special codes.
-
- When the user types anything at the keyboard, it is sent to the remote
- system unless it is an extended code. The three ADM3A commands, Break,
- Dial, and Quit, are initiated by extended codes, which are two-byte
- sequences that have a NULL character as the first byte.
-
- When INKEY$ returns an extended code, the value is sent in the form of an
- argument to the DoCommand subprogram, which examines the second byte, the
- scan code, and compares it with a list of command codes. The Alt-b and
- Alt-d key combinations cause other procedures to be invoked. The Alt-q
- command quits the emulator program by closing the communications
- channel, restoring the display screen to full size and normal attribute,
- and homing the cursor. The fourth scan code recognized by DoCommand is
- that of the PC's Del key. On a terminal, the Del key sends the ASCII DEL
- code (127), but the PC does not. So DoCommand converts the internal code
- to the one expected by the remote system.
-
- The Dial procedure implements simple keyboard dialing. When the user
- types Alt-d, the program responds with the prompt "Number:" in the view
- window and waits for a telephone number to be typed. After receiving a
- number, the Dial procedure uses the ATDT string (assuming tone dialing on
- a Hayes(R)-compatible modem) as a prefix for the telephone number and sends
- the dialing command to the modem.
-
- Once connected to the remote system, the user must log in and start a
- terminal session in the manner expected by the remote host. During a
- session, it may be necessary to tell the host to stop whatever it is
- doing, which is usually done by pressing the Break key, if there is one.
- UNIX and XENIX systems will accept a Del key for this purpose, too. The
- BreakSignal procedure, initiated by the Alt-b command, sends a true break
- signal to the host to get its attention. BreakSignal works by setting the
- break bit in the communication port control register, holding it high for
- a period of time that exceeds that of a normal character transmission
- period, and then clearing the break bit.
-
- The delay period, fixed at a half second in this implementation, is
- created by the Delay procedure, which uses the BASIC TIMER function in a
- loop to control the period. Delay gets the starting time and adds the
- requested period to it, then continually checks for the arrival of the
- future time specified by the sum. To prevent machine lockup at midnight,
- or whenever the clock on the PC rolls over its count of seconds to 0, the
- Delay procedure aborts if the current time value drops below the starting
- time.
-
- Received data is similarly analyzed as it comes in. Most characters are
- simply printed on the PC screen in the view window. However, a small set
- of incoming character codes have special meaning: control codes that cause
- absolute and relative cursor positioning and screen clearing. The adm3a
- lacks line and character insert and delete features, so the emulation is
- simple.
-
- If the Esc code is seen, it could signal the start of a cursor-
- positioning command. The cursor position is set by an escape sequence of
- the form Esc=rc, where r is a row number and c is a column number, each
- encoded as a character, and the ASCII space (decimal 32) represents row or
- column 0. The logic of the ADM3A program detects the Esc code and looks
- for the equal sign. If one is seen in the next character position, two
- additional characters are read and converted to row and column numbers
- relative to the view window values (Top and Left), and the cursor is
- positioned. If the character following the Esc code is not an equal
- sign, the program simply prints the escape character and whatever
- follows it.
-
- All other characters are examined by the ProcessInput procedure to see
- whether they should be printed. Some codes are commands. Ctrl-z, for
- example, clears the screen. Care is taken to handle boundary conditions
- as the real adm3a terminal does. A request to move the cursor right when
- it is already at the right window border, for example, uses a LOCATE
- statement to implement the automatic margins feature, which moves the
- cursor to the beginning of the next line, if there is one.
-
- Debugging and testing is aided greatly by the built-in Microsoft
- QuickBASIC debugging features. Figure 10 shows the screen appearance
- after a watch expression, CmdKey$, and a breakpoint on the SELECT CASE
- statement have been set. When you run the program, it stops at the
- breakpoint so you can observe the value of the watch expression. You can
- have multiple watch expressions and breakpoints. Press F5 to continue
- execution after it stops at a breakpoint.
-
- The sample session depicted in Figure 11 shows the ADM3A program accessing
- a XENIX system. It is running the VI editor on the /etc/termcap file,
- showing the termcap entry for the adm3a terminal, among others. Any other
- program that knows how to interact with an adm3a terminal, and most of
- them do, can run successfully with this emulation.
-
- Programs that use fancy line-drawing characters, inverse video, and
- special character formatting will not look too hot on an adm3a and may
- not even run at all. You can use this emulation as a starting point for
- more sophisticated emulations, such as DEC VT100-series terminals. Also,
- you may want to add file transfer and other "intelligent terminal"
- features to round out the program. Microsoft QuickBASIC has what it takes
- to do that and much more.
-
-
- Welcome to the Future
-
- The technology in Microsoft QuickBASIC offers many benefits to its users.
- Microsoft QuickBASIC ushers in the era of significantly higher
- programmer productivity and injects a great deal of fun into what has
- traditionally been drudgery.
-
- Where to from here? Microsoft representatives have said publicly that a
- significant amount of in-house work on current and future language products
- is based on p-code technology, which will be the basis of compiler products
- for other languages and some application programs. I hope that these
- products will be announced in the near future.
-
-
- Figure 2: The Microsoft QuickBASIC Development Cycle
-
- ┌─────────────────────────────────────────────────┐
- │ Microsoft QuickBASIC 3.0 │
- │ ╔════════════╗ ╔════════════╗ │
- │ ║ EDIT ╠══════════════║ COMPILE ║ │
- │ ║ ║ ║ ║ │
- │ ╚═══════════╝══════╗ ╚══════╦═════╝ │
- │ ║ ║ ║ │
- │ ╔═════╩══════╗ ╚═══════╦═══════════╗ │
- │ ║ DEBUG ║ ║ RUN ║ │
- │ ║ ║══════════════╣ ║ │
- │ ╚════════════╝ ╚════════════╝ │
- ├─────────────────────────────────────────────────┤
- │ Microsoft QuickBASIC 4.0 │
- │ ╔════════════╗ ╔════════════╗ │
- │ ║ EDIT ║═══════╗ ║ COMPILE ║ │
- │ ║ ║ ║ ║ ║ │
- │ ╚════════╦══╝════╗ ║ ╚════════════╝ │
- │ ║ ║ ║ ║ │
- │ ╔══╩════════╗ ║ ╚══════╔════════════╗ │
- │ ║ DEBUG ║ ╚═════════╣ RUN ║ │
- │ ║ ║══════════════╣ ║ │
- │ ╚════════════╝ ╚════════════╝ │
- └─────────────────────────────────────────────────┘
-
-
- Figure 3: Microsoft QuickBASIC 4.0 p-code
-
- ┌───────────────────────────────────────────────────┬────────┐
- │ ╔═════════════════════╗ │ │
- │ ┌─────────║ ASCII ║ │ Disk │
- │ │ ║ TEXT ║ │ or │
- │ │ ╚════════════════╤════╝ │ Screen │
- ├───────│─────────────────────────PARSER────────────├────────┤
- │ │ │ │
- │ │ ╔═════════════════════╗ │ M │
- │ │ ║ PARSED ║ │ │
- │ │─────────╢ ║ │ e │
- │ │ ╚════════════════╤════╝ │ │
- │ LISTER BINDER │ m │
- │ │ ╔════╧════════════════╗ │ │
- │ │ ║ SYMBOLIC ║ │ o │
- │ │─────────╢ ║ │ │
- │ │ ╚════════════════╤════╝ │ r │
- │ │ BINDER │ │
- │ │ ╔════╧════════════════╗ │ y │
- │ │ ║ THREADED ║ │ │
- │ └──────────╢ ║ │ │
- │ ╚═════════════════════╝ │ │
- └───────────────────────────────────────────────────┴────────┘
-
-
- Figure 4: The Microsoft QuickBASIC 4.0 Programming Environment
-
- ╔═════════╗
- ║ BAS ╟────┐
- ╚═════════╝ │ ┌─────────┐ ╔═════════╗
- │ ┌─────────│ LIB ├───║ LIB ║
- ╔═════════╗ │ ┌────────┐ ┌───┴────┐ └────┬────┘ ╚═════════╝
- ║ BI ╟──┐ └──│ │ │ │ │
- ╚═════════╝ └────│ │ │ │ ┌────────┐ ╔═════════╗
- │ QB ├────│ BC ├────│ LINK ├───║ EXE ║
- ╔═════════╗ ┌────│ │ │ │ └─────────┘ ╚═════════╝
- ║ MAK ╟──┘ ┌──│ │ │ │
- ╚═════════╝ │ └────────┘ └────────┘
- │
- ╔═════════╗ │
- ║ QLB ╟────┘
- ╚═════════╝
-
-
- Figure 5: Mixed-Language Programming
-
- ╔═════════╗ ┌────────┐
- ║ BAS ╟─│ │
- ╚═════════╝ │ BC │
- │ │ ╔═════════╗
- ╔═════════╗ │ │──║ OBJ ╟───────┐
- ║ BI ╟─└────────┘ ╚═════════╝ │
- ╚═════════╝ │
- │
- ╔═════════╗ ┌────────┐ ┌───────┐ ╔═════════╗ ┌──────┐
- ║ C ╟─│ │ │ │─║ EXE ╟─│ │
- ╚═════════╝ │ CL │ ╔═════════╗ │ │ ╚═════════╝ │ │
- │ │──║ OBJ ╟──│ LINK │ ╔═════════╗ │ CV │
- ╔═════════╗ │ │ ╚═════════╝ │ │─║ QLB ╟─│ │
- ║ H ╟─└────────┘ └───────┘ ╚═════════╝ └──────┘
- ╚═════════╝ │
- │
- ╔═════════╗ ┌────────┐ ╔═════════╗ │
- ║ ASM ╟─│ MASM │──║ OBJ ╟───────┘
- ╚═════════╝ └────────┘ ╚═════════╝
-
-
- Figure 6: Example of the TYPE Keyword
-
- TYPE CardType
-
- PartNumber AS STRING * 20
- Description AS STRING * 40
- UnitCost AS SINGLE
- StockQty AS INTEGER
- ReorderQty AS INTEGER
- CurrentQty AS INTEGER
- Supplier AS SupplyType
- END TYPE
-
-
- Figure 7: Video Terminal Emulator
-
- ' ADM3A Terminal Emulator
- ' Version 1.0
- '
- ' This program emulates a Lear Siegler adm3a video terminal. The
- ' emulator gives PC users the ability to run full-screen video
- ' programs on UNIX and XENIX systems, and others that support a
- ' video terminal interface.
- '
- ' Author: Augie Hansen
- ' Released: 1-14-88
-
-
- DEFINT A-Z
-
- '─ Subprogram declarations.
- DECLARE SUB BreakSignal ()
- DECLARE SUB Delay (Period!)
- DECLARE SUB Dial ()
- DECLARE SUB DoCommand (CmdKey$)
- DECLARE SUB InitScreen ()
- DECLARE SUB ProcessInput (Code$)
-
- '─ Manifest constants.
- CONST TRUE = -1, FALSE = NOT TRUE
- CONST BLACK = 0, BLUE = 1, GREEN = 2, CYAN = 3
- CONST RED = 4, MAGENTA = 5, BROWN = 6, WHITE = 7
- CONST BRIGHT = 8, BLINK = 128
- CONST CURSOROFF = 0, CURSORON = 1
- CONST BUFSIZE = 512
- CONST SPACE = 32
- CONST ROWS = 25, COLS = 80
- CONST BANNERCOL = 4, COMMANDCOL = 33
- CONST TESTMODE = 0
-
- '─ Screen management data.
- TYPE WinType
- Top AS INTEGER
- Left AS INTEGER
- Bottom AS INTEGER
- Right AS INTEGER
- Fgnd AS INTEGER
- Bkgnd AS INTEGER
- Standout AS INTEGER
- END TYPE
-
- DIM CmdWin AS WinType
- DIM ViewWin AS WinType
-
- CmdWin.Top = 1
- CmdWin.Bottom = 1
- CmdWin.Left = 1
- CmdWin.Right = 80
- CmdWin.Fgnd = BLACK
- CmdWin.Bkgnd = WHITE
- CmdWin.Standout = BROWN + BRIGHT
-
- ViewWin.Top = 2
- ViewWin.Bottom = 25
- ViewWin.Left = 1
- ViewWin.Right = 80
- ViewWin.Fgnd = WHITE
- ViewWin.Bkgnd = BLUE
- ViewWin.Standout = WHITE + BRIGHT
-
- '─ Set cursor-positioning offsets.
- RowOffset = SPACE - ViewWin.Top
- ColOffset = SPACE - ViewWin.Left
-
- '─ Install an error-recovery mechanism.
- ON ERROR GOTO ErrorRecovery
-
- '─ Set up the emulator screen.
- InitScreen
-
- '─ Set communications parameters.
- Parm$ = ENVIRON$("COMPARMS") ' Check environment.
- IF Parm$ = "" THEN
- Parm$ = "COM2:1200,E,7,1" ' Use defaults.
- END IF
-
- Port$ = LEFT$(Parm$, 4)
- IF Port$ = "COM1" THEN
- PortAddress = &H3FB
- ELSE
- PortAddress = &H2FB
- END IF
- BreakMask = &H40 ' Break control bits
-
- '─ Open the communications channel.
- OPEN Parm$ FOR RANDOM AS #1 LEN = BUFSIZE
-
- '
- ' Main communications loop.
- '
- ' Check the keyboard for input. Send all normal characters typed by
- ' the user to the communications port for transmission to the remote
- ' system. If the user presses any of the emulator command keys, run
- ' the associated procedure.
- '
- Main:
- EscapeFlag = FALSE
- DO
- '─ Process keyboard input for commands and characters.
- UserKey$ = INKEY$
- IF LEN(UserKey$) > 1 THEN
- DoCommand UserKey$
- ELSEIF UserKey$ <> "" THEN
- '─ Send the character to the remote system.
- PRINT #1, UserKey$;
- END IF
-
- '─ Check the communications line for received characters.
- DO
- IF EOF(1) THEN
- EXIT DO
- END IF
- Received$ = INPUT$(1, #1) ' Read a single character.
-
- '─ Look for cursor-positioning command.
- IF EscapeFlag = TRUE THEN
- IF Received$ = "=" THEN
- CursorRow = ASC(INPUT$(1, #1))-RowOffset
- CursorCol = ASC(INPUT$(1, #1))-ColOffset
- LOCATE CursorRow, CursorCol
- ELSE
- PRINT CHR$(27); ' The retained Esc code.
- PRINT Received$;
- END IF
- EscapeFlag = FALSE
- ELSE
- ProcessInput Received$
- END IF
- LOOP
- LOOP
-
- END
-
- ErrorRecovery:
- RESUME Main
-
- '
- ' BreakSignal
- '
- ' Send a 'break' signal to the communications port.
- '
- '
- SUB BreakSignal
- SHARED PortAddress, BreakMask
-
- '─ Set the break bit.
- OUT PortAddress, (INP(PortAddress) OR BreakMask)
-
- '─ Mark time for the break period.
- Delay .5
-
- '─ Clear the break bit.
- OUT PortAddress, (INP(PortAddress) AND NOT BreakMask)
- END SUB
-
- '
- ' Delay
- '
- ' Produce a specified delay. The delay period is specified in
- ' seconds as a single-precision number with tenth-second precision.
- '
- '
- SUB Delay (Period!) STATIC
- Start! = TIMER
-
- '─ Loop for specified period. Abort if clock rolls over.
- DO
- Now! = TIMER
- IF (Now! - Start! < Period!) OR (Now! < Start!) THEN
- EXIT SUB
- END IF
- LOOP
- END SUB
-
- '
- ' Dial
- '
- ' Ask the user for a telephone number and dial it.
- '
- '
- SUB Dial
- INPUT "Number: ", Phone$
- PRINT #1, "ATDT" + Phone$
- END SUB
-
- '
- ' DoCommand
- '
- ' Examine the extended key code to see whether it is an Emulator
- ' program command. If it is, execute the requested command. If it
- ' is not, return to the caller without doing anything.
- '
- '
- SUB DoCommand (CmdKey$) STATIC
- SHARED CmdWin AS WinType
- SELECT CASE ASC(RIGHT$(CmdKey$, 1))
- CASE 16 ' Alt+q - Quit the emulator.
- ' Close the communications channel.
- CLOSE
- ' Restore full screen.
- VIEW PRINT
- ' Clear the screen and "home" the cursor.
- COLOR WHITE, BLACK
- CLS
- LOCATE CmdWin.Top, CmdWin.Left, CURSORON
- END
- CASE 32 ' Alt+d - Dial a number
- Dial
- CASE 48 ' Alt+b - Send break signal.
- BreakSignal
- CASE 83 ' PC keyboard Del key - Send an ASCII DEL
- PRINT #1, CHR$(127);
- CASE ELSE
- ' Unknown command - ignore it.
- END SELECT
- END SUB
-
- '
- ' InitScreen
- '
- ' Set up command bar (1 line), guarantee that the cursor is turned
- ' on, and establish the active terminal display window (24 lines).
- '
- '
- SUB InitScreen STATIC
- SHARED CmdWin AS WinType, ViewWin AS WinType
-
- '─ Initialize the screen for text and 80 columns.
- SCREEN TEXTMODE
- WIDTH COLS, ROWS
- COLOR WHITE, BLACK
- CLS
-
- '─ Draw the command window on the top line.
- LOCATE CmdWin.Top, CmdWin.Left, CURSORON
- COLOR CmdWin.Fgnd, CmdWin.Bkgnd
- PRINT SPACE$(CmdWin.Right - CmdWin.Left + 1)
-
- '─ Display the program banner.
- LOCATE CmdWin.Top, CmdWin.Left + BANNERCOL
- COLOR CmdWin.Standout, CmdWin.Bkgnd
- PRINT "ADM3A EMULATOR";
-
- '─ Display a command summary.
- LOCATE CmdWin.Top, CmdWin.Left + COMMANDCOL
- COLOR CmdWin.Fgnd, CmdWin.Bkgnd
- PRINT "Break (Alt+b) Dial (Alt+d) Quit (Alt+q)"
-
- '─ Initialize the terminal screen.
- VIEW PRINT ViewWin.Top TO ViewWin.Bottom
- COLOR ViewWin.Fgnd, ViewWin.Bkgnd
- CLS
- END SUB
-
- '
- ' ProcessInput
- '
- ' Check input from the communications line and analyze it. Act on
- ' any adm3a terminal commands codes. Pass anything else unchanged
- ' to the terminal screen.
- '
- '
- SUB ProcessInput (Code$) STATIC
- SHARED EscapeFlag, ViewWin AS WinType
- SELECT CASE ASC(Code$)
- CASE 8 ' ASCII backspace character
- IF POS(0) > ViewWin.Left THEN
- ' Nondestructive backspace
- LOCATE , POS(0) - 1
- END IF
- CASE 10 ' ^J - New-line character
- IF CSRLIN < ViewWin.Bottom THEN
- LOCATE CSRLIN + 1
- ELSE
- PRINT Code$;
- END IF
- CASE 11 ' ^K - Up-line command
- IF CSRLIN > ViewWin.Top THEN
- LOCATE CSRLIN - 1
- END IF
- CASE 12 ' ^L - Form-feed character
- ' adm3a use as nondestructive space
- IF POS(0) < ViewWin.Right THEN
- LOCATE , POS(0) + 1
- ELSEIF (POS(0) = ViewWin.Right) AND _
- (CSRLIN < ViewWin.Bottom) THEN
- LOCATE CSRLIN + 1, ViewWin.Left
- END IF
- CASE 13
- LOCATE , ViewWin.Left
- CASE 26 ' ^Z - Clear the screen
- CLS
- CASE 27 ' Esc - Could be start of cursor sequence
- EscapeFlag = TRUE
- CASE 30 ' ^^ - Cursor to home position
- LOCATE ViewWin.Top, ViewWin.Left
- CASE ELSE
- PRINT Code$;
- END SELECT
- END SUB
-
- ████████████████████████████████████████████████████████████████████████████
-
- Debug Microsoft Windows Programs More Effectively with a Simple Utility
-
- Kevin P. Welch
-
- Debugging is an indispensable and unavoidable step in the development of
- any software program. With conventional MS-DOS(R) programs, debugging
- usually starts with the incorporation of numerous print statements into
- the program. If this doesn't work, the battle escalates to sessions with
- DEBUG, CodeView(R), or even to utilizing a hardware-level debugger.
-
- Unfortunately for Windows programmers, many of these traditional
- techniques won't work in the Windows environment. The last few months have
- seen significant improvements in Windows debugging──most notably the
- announcement of CodeView for Windows──but programmers continue to struggle
- with the difficult task of debugging and fine-tuning Windows
- applications.
-
-
- Approaches to Debugging
-
- From a theoretical perspective, there are many ways to classify software
- debugging methods. One common approach breaks debugging into three
- general categories of invasive, noninvasive, and combined techniques.
-
- Invasive debugging typically involves modifying and recompiling
- application source code. The debugging tools and their entry points simply
- become part of the program, which makes the technique easy to implement.
- Two examples of this approach are hard-coded message boxes and print
- statements whose output is directed to display or temporary log files.
-
- This approach often suffices to catch a large proportion of coding and
- algorithmic errors. With skilled use, this method can serve as the major
- debugging tool for large and complicated applications.
-
- However, this approach requires that you have access to the source code in
- question (a problem when you are debugging an interaction with a program
- you didn't write). In addition, the mere inclusion of the debugging
- statements or tools into the application sometimes changes the very
- character of the problem, further complicating the situation. This is
- especially true with system-related issues like memory management and
- interprocess communication.
-
- Noninvasive debugging does not involve modification or recompilation of
- an application. The debugging tool and its associated control functions
- are external to the application and can usually be applied to an existing
- program. Some examples of this approach include message loggers
- (essentially a form of application subclassing), journaling hooks, alias
- libraries, execution profilers, and even strict hardware-level
- debuggers.
-
- If used correctly, the noninvasive approach can provide a great deal of
- externally derived diagnostic information. This technique is especially
- useful in debugging complex interactions between applications.
- Furthermore, using debugging tools that are external to the
- application provides a limit on the possible compounding of errors.
-
- On the negative side, this approach is often complicated, it only reports
- on externally discernable events, and it tends to generate quantities of
- unimportant information surrounding a few vital pieces of data. The
- programmer usually has to do the interpreting of events reported, sorting
- and searching for what is really important.
-
- Combined debugging uses both invasive and noninvasive techniques. In most
- cases, the program need only be recompiled with a different compiler
- switch, which generates symbol files or other output useful to the
- debugging tool. Some examples of this approach include CodeView, Symdeb,
- and other more complicated hardware-debugging tools capable of working
- with application internals.
-
- This approach probably represents the most powerful of these debugging
- techniques, and is capable of capturing and describing even the most
- insidious coding or system error. Also, while tracking the problem the
- programmer can usually inspect internal data structures, which provides
- even greater insight into a given situation.
-
- To use this approach effectively, the programmer needs to have an
- intimate understanding of both the underlying operating system and the
- hardware. The power of this technique is offset by a steep learning curve,
- and the generation of a great deal of information. Many programmers
- hesitate to use it except for the most puzzling and difficult of
- problems.
-
-
- Windows Debug Utility
-
- After developing a number of Windows applications and struggling with
- the problems of debugging, I decided to develop a simple invasive
- debugging tool that would provide reasonable insight into the operations
- that occurred within a troublesome application.
-
- This effort resulted in the Windows Debug utility, a small object module
- (about 4Kb compiled) that can be conditionally linked into your
- program. It provides print statementlike debugging capabilities within the
- Windows environment.
-
- Once integrated into your application, the tools provided by the utility
- can easily be used to display or record a variety of internal program
- events. The entire debugging process is managed with a central control
- panel (see Figure 1) accessible through the system menu of the
- application. Using this control panel you can:
-
- ■ turn debugging ON or OFF
- ■ filter out unwanted debugging events
- ■ direct debugging output to the display and/or a log file
-
-
- Debug Utility Functions
-
- The debug module linked into your application is built from three separate
- files. Using the files DEBUG (make file for debug module), DEBUG.H (header
- file containing definitions and function declarations), and DEBUG.C
- (source code for debug utilities), you can create the object module and
- integrate it into your program. (Due to space limitations, the code for
- the DEBUG utility could not be included. It must be downloaded from any
- of our bulletin boards. -Ed.)
-
- The debug module consists of four functions, three of which are referenced
- explicitly within your host application.
-
- The first of these is the DebugSetup function, which is responsible for
- creating the debug viewport window and appending the Debug... control
- panel menu option to the system menu of the main application window. To
- call this function you must provide a window handle, a debug control panel
- message, and the maximum number of lines to be maintained by the debug
- viewport.
-
- The DebugSetup function assumes that there is a system menu associated
- with the main application window handle that you provide. If your main
- window does not contain a system menu and you wish to use some other
- means to provide access to the debug control panel, you can easily do so.
-
- Whenever the Debug... option is appended to the system menu (see
- Figure 2), the control panel message number is associated with the option
- and is returned. The actual message number is returned in the wParam
- portion of the WM_SYSCOMMAND message. To display the control panel you
- have to explicitly trap this message and call the DebugControl function.
-
- The parameter specifying maximum viewport lines in DebugSetup limits the
- amount of memory consumed by the utility while you view debug statements
- in the viewport. The debug viewport consists of a movable listbox
- containing a series of formatted debugging statements. Debugging
- statements displayed in this viewport are successively appended until
- the specified maximum is reached. At that point, each additional
- debugging statement displaces an earlier one. Although the maximum
- number of debugging statements maintained by the viewport is limited
- only by memory, in most situations you should limit the number to less
- than 100.
-
- The DebugControl function lets you display the debug control panel and
- change the current debugging options. This function is normally called as
- part of the WM_SYSCOMMAND message handling code, which is invoked whenever
- the user selects the Debug... option from the system menu. This function
- calls DebugControlDlgFn using the predefined DebugControl dialog box
- resource.
-
- DebugControlDlgFn is responsible for processing all the messages related
- to the debug control panel dialog box. The initialization section (under
- WM_INITDIALOG) of this function translates the current global debug-
- options data structure into corresponding button states and edit fields.
- The command section (under WM_COMMAND) processes the various button
- messages and accordingly enables or disables fields. The termination
- section (under BUG_OK) retrieves the current control panel status and
- translates it back into the global options data structure. At the same
- time it parses (albeit in a crude manner) the filter edit field and
- defines a list of accepted debug statement classification codes.
-
- Finally, the Debug function generates formatted debugging statements and
- is typically called throughout your application. The first parameter to
- this function is the statement classification code, which is used by
- the filter section of the Debug function. If the debug filter is active,
- this classification code is checked against a list of to-be-filtered
- categories. Only messages with codes on this list are displayed or
- written to disk, enabling you to filter out messages you're not
- interested in.
-
- For example, you could arbitrarily assign a classification code of 1 to
- all paint-related events or processes, a code of 2 to clipboard-related
- ones, and a code of 3 to all others. If you wished to exclude all but
- clipboard- and paint-related debugging statements, you would activate
- the filter from the control panel with a range of 1,2. All statements
- assigned code 3 would be excluded from the viewport or log-file listing,
- yielding an abbreviated execution profile that would let you concentrate
- on particular facets of your program.
-
- The second parameter to the Debug function is a format control string
- defining the physical contents of the resulting statement. This control
- string is identical to that used by the standard C printf function. The
- parameters following this format string are used in conjunction with
- the format control string and are converted and output accordingly.
- Figure 3 illustrates how the Debug function might be used.
-
- Note how the variable number of parameters is handled by the Debug
- function. Unless you increase the size of the pass-by-value structure, you
- are limited to 64 bytes of parameter storage. You may wish to experiment
- with alternative ways of handling the parameter list. One suggestion is
- to use the C 5.0 vprintf function along with either the ANSI C or UNIX(R)
- System V variants.
-
-
- Integrating the Debug Utility
-
- Before you can use the tools provided by the debug module to probe the
- workings of your application, you must perform several steps.
-
- Copy the following files to the directory where you are creating your
- application:
-
- DEBUG.H
- DEBUG.C
-
- Define a new compiler command line variable in your application make
- file. This variable can be used within a conditional compilation
- pragma, thus controlling the generation of debugging code. For example,
- you might define and use the compiler flags seen in Figure 4.
-
- Modify your application's make file so that the module DEBUG.OBJ is
- included whenever you build your program. See Figure 5 for an example.
-
- Define a debug control message number in your application header file.
- This message is returned by Windows whenever you select the Debug...
- option from the system menu.
-
- Export DebugControlDlgFn by defining it in your application DEF file.
- Failure to do this will result in erratic behavior. For example, you might
- change the export section of your DEF file from
-
- EXPORTS
- TestWndFn @1
- to
-
- EXPORTS
- TestWndFn @1
- DebugControlDlgFn @2
-
- Add the lines shown in Figure 6 to your application's resource file.
-
- Call the DebugSetup function shortly after creating your main application
- window. Be sure your main window has a system menu and that you pass a
- valid handle to this function. Note that it is good practice to enclose
- this function call within a conditional compilation pragma:
-
- #if DEBUG
- DebugSetup( hMainWnd, BUG_CONTROL, 100 );
- #endif
-
- Include the DEBUG.H file in each source code module for which you use
- routines from the debug utility. The header file includes function
- definitions and calling conventions necessary to the routines.
-
- Define debugging statements throughout your program in areas of particular
- interest. Try to categorize your debugging statements.
-
- Note that debugging by default is OFF and that statements encountered
- before it is explicitly activated are ignored. One example of this is the
- use of debugging statements to probe the CREATEWINDOW message
- processing section of your main window function.
-
- Recompile and link your application.
-
-
- Using the Debug Utility
-
- To get the best understanding of the debug utility let's look at a sample
- application like BUGTEST.EXE. Figure 7 shows how to create this
- application, with each file containing references to the debug utility.
-
- Throughout the journal listings of these files the sections which
- reference the debug utility are clearly identified. You can use these
- changes as a template when you incorporate the utility into your own
- applications.
-
- Pay special attention to the TestWndFn in the BUGTEST.C file (see Figure 8
- for the code listings for BUGTEST). This window function contains several
- explicit references to the debug utility. First you see a series of
- debugging statements that output the contents of several Windows messages.
- Note how the messages are classified and remember that you can filter
- categories to limit the statements generated.
-
- You should also notice the special handling of the BUG_CONTROL message in
- the WM_SYSCOMMAND processing section of the function. This message is
- generated whenever you access the Debug... option from the system menu.
- The call to DebugControl displays the debug control panel from which you
- can activate or deactivate debugging and define various output options.
-
- To create BUGTEST.EXE you need Microsoft C 5.0, Windows 2.03, and the
- Windows 2.03 Software Development Kit (SDK). If you don't have the 2.03
- SDK, you can probably get everything to work correctly (with perhaps minor
- modifications) using the 1.04 SDK. Although the BUGTEST.ICO file is not
- included in the code listings, you can easily create your own icon with
- the editor in the Windows SDK.
-
- Once you have built the BUGTEST application, you can experiment with the
- debug utility. When you first bring up the program, a standard pop-up
- window appears. Using either the mouse or the keyboard you can move,
- resize, and make this window into an icon just as you would any other
- window. Whenever you perform one of these operations, numerous debugging
- statements are generated, but they are ignored by the debug utility until
- you explicitly turn debugging ON through the control panel.
-
- To access the control panel you select the Debug... option from the system
- menu. The debug control panel is displayed. From this dialog box you can
- activate debugging and define options. You can check the count and display
- boxes, which causes debugging statements to be enumerated and the results
- displayed in a pop-up listbox (see Figure 9). Using this listbox you can
- watch the debugging statements as they are generated by the main
- application (see Figure 10).
-
- Remember that the filter option allows you to select particular
- categories of debugging statements. Also note that this implementation of
- the debug utility is set for 32 filter categories. If these are
- insufficient, you can easily change the MAX_FILTER definition in DEBUG.C
- to a larger number and recompile the utility.
-
- The log-file option works almost identically to the display option, except
- that debugging statements are appended to the disk file you specify. You
- can simultaneously display and log, although your system will slow
- considerably. When using log files I add the following line to the
- "extensions section" of my WIN.INI file:
-
- LOG=NOTEPAD.EXE ^.LOG
-
- This lets you double-click on any log file and view its contents using the
- notepad provided with Windows. Be aware that notepad is not capable of
- loading large log files, but this shouldn't be a major problem.
-
-
- Conclusion
-
- The debug utility is not about to displace the likes of Windows CodeView.
- However, for simple debugging it can provide a great deal of insight into
- the workings of an application and should be a useful addition to every
- programmer's toolbox.
-
- The debug utility is capable of solving a large number of Windows
- problems. With the code provided here as a framework, you may extend this
- utility into many interesting areas. You could use journaling hooks to
- create a general purpose message tracker to explore problems between
- applications. You could experiment with multiple debug viewports into a
- single application. Or you could move the utility into a dynamically
- linked library and debug multiple applications simultaneously. These,
- and hopefully many other creative enhancements are left to you.
-
-
- Figure 3: A Sample Use of the DEBUG Utility
-
- Debug( 1, "WM_SIZE: [%u,%u], LOWORD(lParam),HIWORD(lParam));
- Debug( 2, "WM_MOVE: [%u,%u]", LOWORD(lParam),IWORD(lParam));
- Debug( 3, "WM_CHAR: [%c,%u]", wParam, wParam );
- Debug( 4, "WM_ACTIVATE: [%s]", (wParam)?"on":"off" );
-
-
- Figure 4: Sample Compiler Flags for Compiling the DEBUG Utility Code
-
- STDFLAGS=-c -u -AS -FPa -Gsw -Os -Zep
- BUGFLAGS=-c -u -AS -FPa -Gsw -Os -Zep -DDEBUG
-
- bugtest.obj: bugtest.c
- cl $(BUGFLAGS) bugtest.c
-
-
- Figure 5: Sample Make File to Include DEBUG within an Application
-
- Change your make file from
-
- bugtest.exe: bugtest.obj bugtest.def bugtest.res
- link4 bugtest /AL:16 /NOE,,,slibw,bugtest.def
-
- to
-
- bugtest.exe: bugtest.obj debug.obj bugtest.def bugtest.res
- link4 bugtest+debug /AL:16 /NOE,,,slibw,bugtest.def
-
-
- Figure 6: Debug Control Panel Definitions
-
- #include "debug.h"
-
- DebugControl DIALOG LOADONCALL MOVEABLE DISCARDABLE 8, 20, 185, 81
- STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_POPUP
- BEGIN
- CONTROL "OFF - debug &inactive." BUG_OFF,"button",STD_BUTTON,5,3,130,12
- CONTROL "ON - debug &active." BUG_ON,"button",STD_BUTTON,5,15,128,12
- CONTROL "&Count debug events." BUG_COUNT,"button",STD_CHECKBOX,8,26,114,12
- CONTROL "&Display in debug window." BUG_DISPLAY,"button",STD_CHECKBOX,18,38,
- CONTROL "&Filter" BUG_FILTER,"button",STD_CHECKBOX,18,50,34,12
- CONTROL "" BUG_FILTERLIST,"edit",STD_EDITFIELD,55,50,78,12
- CONTROL "&Log to" BUG_LOG,"button",STD_CHECKBOX,18,62,36,12
- CONTROL "" BUG_LOGFILE,"edit",STD_EDITFIELD ,55,63,78,12
- CONTROL "" -1,"static",SS_BLACKFRAME | WS_CHILD,142,0,1,81
- CONTROL "Ok" BUG_OK,"button",STD_BUTTON,148,4,32,14
- CONTROL "Cancel" BUG_CANCEL,"button",STD_BUTTON,148,21,32,14
- END
-
-
- Figure 7: A list of the files required to build the sample test program that
- incorporates the DEBUG utility.
-
- BUGTEST application make file
- BUGTEST.C application source code
- BUGTEST.RC application resource file
- BUGTEST.ICO application icon (referenced by resource file)
- BUGTEST.DEF application link definition
-
-
- Figure 8:
-
- Make File for BUGTEST
-
- STDFLAGS=-c -u -AS -FPa -Gsw -Os -Zep
- BUGFLAGS=-c -u -AS -FPa -Gsw -Os -Zep -DDEBUG
-
- bugtest.res: bugtest.rc bugtest.ico
- rc -r bugtest.rc
-
- debug.obj: debug.h debug.c
- cl $(STDFLAGS) debug.c
-
- bugtest.obj: bugtest.c
- cl $(BUGFLAGS) bugtest.c
-
- bugtest.exe: bugtest.obj bugtest.def bugtest.res debug.obj
- link4 bugtest+debug /AL:16 /NOE,,,slibw,bugtest.def
- rc bugtest.res
-
- Code Listing for BUGTEST
-
- /*
- * DEBUG TEST PROGRAM - SOURCE CODE
- *
- * LANGUAGE : Microsoft C 5.0
- * MODEL : small
- * STATUS : operational
- *
- * 12/11/87 1.00 - Kevin P. Welch - initial creation.
- *
- */
-
- #include <windows.h>
- #include "debug.h"
-
- /* local definitions */
- #define BUG_CONTROL 201
-
- /* function definitions */
- LONG FAR PASCAL TestWndFn( HWND, WORD, WORD, LONG );
-
- /**/
-
- /*
- * MAINLINE - BUG TEST PROGRAM
- *
- * This mainline initializes the test program and processes
- * and dispatches all messages relating to the debug test
- * window.
- *
- */
-
- int PASCAL WinMain( hInstance, hPrevInstance, lpsCmd, wCmdShow )
- HANDLE hInstance;
- HANDLE hPrevInstance;
- LPSTR lpsCmd;
- WORD wCmdShow;
- {
- /* local variables */
- MSG Msg; /* current system message */
-
- /* initialization */
- if ( TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow ) ) {
-
- /* process system messages until finished */
- while ( GetMessage( (LPMSG)&Msg, NULL, 0, 0 ) ) {
- TranslateMessage( (LPMSG)&Msg );
- DispatchMessage( (LPMSG)&Msg );
- }
-
- /* terminate application */
- exit( Msg.wParam );
-
- } else
- exit( FALSE );
-
- }
-
- /**/
-
- /*
- * TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow ) : BOOL;
- *
- * hInstance current instance handle
- * hPrevInstance handle to previous instance
- * lpsCmd command line string
- * wCmdShow window display flag
- *
- * This utility function performs all the initialization required
- * for testing the debug utility. Included in this program is
- * the registry and creation of the main window & the installation
- * of the debug utility code.
- *
- */
-
- static BOOL TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow )
- HANDLE hInstance;
- HANDLE hPrevInstance;
- LPSTR lpsCmd;
- WORD wCmdShow;
- {
- /* local variables */
- HWND hWnd; /* current window handle */
- BOOL bResult; /* result of initialization */
- WNDCLASS WndClass; /* window class */
-
- /* initialization */
- bResult = FALSE;
-
- /* register window class */
- if ( !hPrevInstance ) {
-
- /* define MAZE window class */
- memset( &WndClass, 0, sizeof(WNDCLASS) );
- WndClass.lpszClassName = (LPSTR)"TestWindow";
- WndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
- WndClass.lpszMenuName = (LPSTR)NULL;
- WndClass.style = CS_HREDRAW | CS_VREDRAW;
- WndClass.lpfnWndProc = TestWndFn;
- WndClass.hInstance = hInstance;
- WndClass.hIcon = LoadIcon( hInstance, "BugTestIcon" );
- WndClass.hbrBackground = (HBRUSH)(COLOR_MENU + 1);
-
- /* register maze window class */
- if ( RegisterClass( (LPWNDCLASS)&WndClass ) ) {
-
- /* create window */
- hWnd = CreateWindow(
- "TestWindow", /* class name */
- "Debug Test Window", /* caption */
- WS_TILEDWINDOW, /* style */
- CW_USEDEFAULT, /* x position */
- CW_USEDEFAULT, /* y position */
- CW_USEDEFAULT, /* width */
- CW_USEDEFAULT, /* height */
- (HWND)NULL, /* parent window */
- (HMENU)NULL, /* menu */
- hInstance, /* application */
- (LPSTR)NULL /* other data */
- );
-
- /* continue if successful */
- if ( hWnd ) {
-
- /* Here is where the debug utility is
- * installed into the program. A response
- * message number is provided along with the
- * maximum number of debug statements which
- * will be maintained by the listbox. The
- * larger this number, the less global memory
- * available for your application.
- */
- #if DEBUG
-
- DebugSetup( hWnd, BUG_CONTROL, 100 );
- #endif
-
- /* make window visible */
- bResult = TRUE;
- ShowWindow( hWnd, wCmdShow );
-
- }
-
- }
-
- }
-
- /* return result */
- return( bResult );
-
- }
-
- /**/
-
- /*
- * TEST WINDOW MESSAGE PROCESSING PROCEDURE
- *
- * TestWndFn( hWnd, wMessage, wParam, lParam ) : LONG FAR PASCAL
- *
- * hWnd window handle
- * wMessage message number
- * wParam additional message information
- * lParam additional message information
- *
- * This window function processes all the messages related to
- * the debug test window. Using the system menu the user can
- * display the debug control panel dialog box.
- *
- */
-
- LONG FAR PASCAL TestWndFn( hWnd, wMessage, wParam, lParam )
- HWND hWnd;
- WORD wMessage;
- WORD wParam;
- LONG lParam;
- {
- /* local variables */
- LONG lResult; /* result of message */
-
- /* initialization */
- lResult = FALSE;
-
- #if DEBUG
- /* sample debugging output */
- switch( wMessage )
- {
- case WM_MOVE :
- Debug( 1, "WM_MOVE: [%u,%u]", HIWORD(lParam),
- LOWORD(lParam) );
- break;
- case WM_SIZE :
- Debug( 1, "WM_SIZE: [%u,%u]", HIWORD(lParam),
- LOWORD(lParam) );
- break;
- case WM_CHAR :
- Debug( 2, "WM_CHAR: [%c,%u]", wParam, wParam );
- break;
- case WM_ACTIVATE :
- Debug( 3, "WM_ACTIVATE: %s",
- (wParam)?"activate":"inactivate" );
- break;
- case WM_ACTIVATEAPP :
- Debug( 3, "WM_ACTIVATEAPP: %s",
- (wParam)?"activate":"inactivate" );
- break;
- case WM_PAINT :
- Debug( 4, "WM_PAINT:" );
- break;
-
- default :
- break;
- }
- #endif
-
- /* process each message */
- switch( wMessage )
- {
- case WM_SYSCOMMAND : /* system command */
-
- /* In here you need to handle the special case where the
- * user asks for the debug control panel to be
- * displayed.
- * To do so you need to trap the control panel response
- * message you provided when installing the debug
- * utility.
- */
-
- /* process sub-message */
- switch( wParam )
- {
- #if DEBUG
- case BUG_CONTROL : /* debug control panel */
- DebugControl( hWnd );
- break;
- #endif
- default :
- lResult = DefWindowProc( hWnd, wMessage, wParam,
- lParam );
- break;
- }
-
- break;
- case WM_DESTROY : /* destroy window */
- PostQuitMessage( 0 );
- break;
- default : /* send to default */
- lResult = DefWindowProc( hWnd, wMessage, wParam,
- lParam );
- break;
- }
-
- /* return normal result */
- return( lResult );
-
- }
-
- DEF File for BUGTEST
-
- NAME BUGTEST
-
- DESCRIPTION 'Debug Test Utility'
-
- STUB '\BIN\WINSTUB.EXE'
-
- CODE MOVEABLE
- DATA MOVEABLE MULTIPLE
-
- HEAPSIZE 4096
- STACKSIZE 4096
-
- EXPORTS
- TestWndFn @1
- DebugControlDlgFn @2
-
- Resource File for BUGTEST
-
- /*
- * DEBUG TEST PROGRAM - RESOURCE FILE
- *
- * LANGUAGE : Microsoft C 5.0
- * MODEL : small
- * STATUS : operational
- *
- * 12/11/87 1.00 - Kevin P. Welch - initial creation.
- *
- */
- #include <style.h>
- #include "debug.h"
-
- BugTestIcon ICON bugtest.ico
-
- DebugControl DIALOG LOADONCALL MOVEABLE DISCARDABLE 8, 20, 185, 81
- CAPTION "Debug Control Panel"
- STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_POPUP
- BEGIN
- CONTROL "OFF - debug &inactive." BUG_OFF,"button",STD_RADIO,5,3,130,12
- CONTROL "ON - debug &active." BUG_ON,"button",STD_RADIO,5,15,128,12
- CONTROL "&Count debug events." BUG_COUNT,"button",STD_CHECKBOX,8,26,114,12
- CONTROL "&Display in debug window." BUG_DISPLAY,"button",STD_CHECKBOX,18,38,
- CONTROL "&Filter" BUG_FILTER,"button",STD_CHECKBOX,18,50,34,12
- CONTROL "" BUG_FILTERLIST,"edit",STD_EDITFIELD,55,50,78,12
- CONTROL "&Log to" BUG_LOG,"button",STD_CHECKBOX,18,62,36,12
- CONTROL "" BUG_LOGFILE,"edit",STD_EDITFIELD ,55,63,78,12
- CONTROL "" -1,"static",SS_FRAME | WS_CHILD,142,0,1,81
- CONTROL "Ok" BUG_OK,"button",DEF_BUTTON,148,4,32,14
- CONTROL "Cancel" BUG_CANCEL,"button",STD_BUTTON,148,21,32,14
- END
-
- ████████████████████████████████████████████████████████████████████████████
-
- An Examination of the Operating Principles of the Microsoft Object Linker
-
- Richard Wilton
-
- MS-DOS(R) object modules can be processed in two ways: they can be grouped
- together in object libraries or they can be linked into executable files.
- All Microsoft language translators are distributed with two utility
- programs that process object modules: the Microsoft Library Manager (LIB)
- creates and modifies object libraries and the Microsoft Object Linker
- (LINK) processes the individual object records within object modules to
- create executable files.
-
- The following discussion focuses on LINK because of its crucial role in
- creating an executable file.
-
-
- What LINK Does
-
- The function of LINK is to translate object modules into an executable
- program. LINK's input consists of one or more object files (OBJ files)
- and, optionally, one or more libraries (LIB files). LINK's output is an
- executable file (EXE file) containing binary data that can be loaded
- directly from the file into memory and executed. LINK can also generate a
- symbolic address map listing (MAP file)──a text file that describes the
- organization of the EXE file and the correspondence of symbols declared
- in the object modules to addresses in the executable file.
-
-
- Building an Executable
-
- LINK builds two types of information into an EXE file. First, it extracts
- executable code and data from the LEDATA and LIDATA records in object
- modules, arranges them in a specified order according to its rules for
- segment combination and relocation, and copies the result into the EXE
- file. Second, LINK builds a header for the EXE file. The header describes
- the size of the executable program and also contains a table of load-time
- segment relocations and initial values for certain CPU registers. See
- Pass 2 in the LINK Internals section. (See MSJ, Vol. 3 No. 2,
- "Exploring the Structure and Contents of the MS-DOS(R) Object Module
- Format," for more information on LEDATA, LIDATA, and other object
- records supported by LINK-Ed.)
-
-
- Relocation and Linking
-
- In building an executable image from object modules, LINK performs two
- essential tasks: relocation and linking. As it combines and rearranges the
- executable code and data it extracts from the object modules it
- processes, LINK frequently adjusts, or relocates, address references to
- account for the rearrangements (see Figure 1). LINK links object modules
- by resolving address references among them. It does this by matching the
- symbols declared in EXTDEF and PUBDEF object records (see Figure 2). LINK
- uses FIXUPP records in order to determine exactly how to compute both
- address relocations and linked address references.
-
-
- Object Module Order
-
- LINK processes input files from three sources: object files and libraries
- specified explicitly by the user (in the command line, in response to
- prompts by LINK, or in a response file) and object libraries named in
- object module COMENT records.
-
- LINK always uses all of the object modules in the object files it
- processes. In contrast, it extracts individual object modules from
- libraries-only those object modules needed to resolve references to public
- symbols are used. This difference is implicit in the order in which LINK
- reads its input files:
-
- ■ object files that are specified in the command line or in response to
- the Object Modules prompt
-
- ■ libraries that are specified in the command line or in response to
- the Libraries prompt
-
- ■ libraries that are specified in COMENT records
-
- The order in which LINK processes object modules influences the
- resulting executable file in three ways. First, the order in which
- segments appear in LINK's input files is reflected in the segment
- structure of the executable file. Second, the order in which LINK
- resolves external references to public symbols depends on the order in
- which it finds the public symbols in its input files. Finally, LINK
- derives the default name of the executable file from the name of the first
- input object file.
-
-
- Segment Order
-
- In general, LINK builds named segments into the executable file in the
- order in which it first encounters the SEGDEF records that declare the
- segments. (The /DOSSEG switch also affects segment order.) This means
- that the order in which segments appear in the executable file can be
- controlled by linking object modules in a specific order. In assembly
- language programs, it is best to declare all the segments used in the
- program in the first object module to be linked so that the segment order
- in the executable file is under complete control.
-
-
- Order in Which References Are Resolved
-
- LINK resolves external references in the order in which it encounters the
- corresponding public declarations. This fact is important as it determines
- the order in which LINK extracts object modules from libraries. When a
- public symbol required to resolve an external reference is declared more
- than once among the object modules in the input libraries, LINK uses the
- first object module that contains the public symbol. This means that the
- actual executable code or data that is associated with a particular
- external reference can be varied by changing the order in which LINK
- processes its input libraries.
-
- For example, imagine that a C programmer has written two versions of a
- function named myfunc() that is called by the program MYPROG.C. One
- version of myfunc() is for debugging; its object module is found in
- MYFUNC.OBJ. The other is a production version whose object module resides
- in MYLIB.LIB. Under ordinary circumstances, the programmer links the
- production version of myfunc() by using MYLIB.LIB (see Figure 3). In order
- to use the debugging version of myfunc(), the programmer explicitly
- includes its object module (MYFUNC.OBJ) when LINK is executed. This causes
- LINK to build the debugging version of myfunc() into the executable file
- because it finds the debugging version in MYFUNC.OBJ before it finds the
- other version in MYLIB.LIB.
-
- To exploit the order in which LINK resolves external references, it is
- important to know LINK's library search strategy: each individual library
- is searched repeatedly (from first library to last, in the sequence in
- which they are input to LINK) until no further external references can be
- resolved.
-
- The example in Figure 4 demonstrates this search strategy. Library
- LIB1.LIB contains object modules A and B, library LIB2.LIB contains object
- module C, and the object file MYPROG.OBJ contains the object module MAIN;
- modules MAIN, A, and C each contain an external reference to a symbol
- declared in another module. When this program is linked with
-
- LINK MYPROG,,,LIB1+LIB2
-
- LINK starts by incorporating the object module MAIN into the executable
- program. It then searches the input libraries until it resolves all the
- external references:
-
- ■ process MYPROG.OBJ, find unresolved external reference to A
- ■ search LIB1.LIB, extract A, find unresolved external reference to C
- ■ search LIB1.LIB again; reference to C remains unresolved
- ■ search LIB2.LIB, extract C, find unresolved external reference to B
- ■ search LIB2.LIB again; reference to B remains unresolved
- ■ search LIB1.LIB again, extract B
- ■ no more unresolved external references, so end library search
-
- The order in which the modules appear in the executable file thus
- reflects the order in which LINK resolves the external references; this,
- in turn, depends on which modules were contained in the libraries and on
- the order in which the libraries are input to LINK.
-
-
- Name of the Executable
-
- If no filename is specified in the command line or in response to the Run
- File prompt, LINK derives the name of the executable file from the name
- of the first object file it processes. For example, if the object files
- PROG1.OBJ and PROG2.OBJ are linked with the command
-
- LINK PROG1+PROG2;
-
- the resulting executable file, PROG1.EXE, takes its name from the first
- object file processed by LINK.
-
-
- Order and Combinations
-
- LINK builds segments into the executable file by applying the following
- sequence of rules:
-
- Segments appear in the executable file in the order in which their
- SEGDEF declarations first appear in the input object modules.
-
- Segments in different object modules are combined if they have the same
- name and class and a public, memory, stack, or common combine type. All
- address references within the combined segments are relocated relative to
- the start of the combined segment.
-
- ■ Segments with the same name and either the public or the memory
- combine type are combined in the order in which they are processed by
- LINK. The size of the resulting segment then equals the total size
- of the combined segments.
-
- ■ Segments with the same name and the stack combine type are overlapped
- so that the data in each of the overlapped segments ends at the same
- address. The size of the resulting segment equals the total size of
- the combined segments. The resulting segment is always paragraph
- aligned.
-
- ■ Segments with the same name and the common combine type are
- overlapped so that the data in each of the overlapped segments starts
- at the same address. The size of the resulting segment equals the
- size of the largest of the overlapped segments.
-
- Segments with the same class name are concatenated.
-
- If the /DOSSEG switch is used, the segments are rearranged in conjunction
- with DGROUP.
-
- These rules allow the programmer to control the organization of
- segments in the executable file by ordering SEGMENT declarations in an
- assembly language source module, which produces the same order of SEGDEF
- records in the corresponding object module, and by placing this object
- module first in the order in which LINK processes its input files.
-
- A typical MS-DOS program is constructed by declaring all executable code
- and data segments with the public combine type, thus enabling the
- programmer to compile the program's source code from separate source
- code modules into separate object modules. When these object modules are
- linked, LINK combines the segments from the object modules according to
- the above rules to create logically unified code and data segments in the
- executable file.
-
-
- Segment Classes
-
- LINK concatenates segments with the same class name after it combines
- segments with the same segment name and class. For example, Figure 5 shows
- the following compiling and linking:
-
- MASM MYPROG1;
- MASM MYPROG2;
- LINK MYPROG1+MYPROG2;
-
- After MYPROG1.ASM and MYPROG2.ASM have been compiled, LINK builds the
- _TEXT and FAR_TEXT segments by combining segments with the same name
- from the different object modules. Then, _TEXT and FAR_TEXT are
- concatenated because they have the same class name ('CODE'). _TEXT is
- before FAR_TEXT in the executable file because LINK encounters the
- SEGDEF record for _TEXT before it finds the SEGDEF record for FAR_TEXT.
-
-
- Segment Alignment
-
- LINK aligns the starting address of each segment it processes according
- to the alignment specified in each SEGDEF record. It adjusts the
- alignment of each segment it encounters regardless of how that segment is
- combined with other segments of the same name or class. (The one
- exception is stack segments, which always start on a paragraph
- boundary.)
-
- Segment alignment is particularly important when public segments with
- the same name and class are combined from different object modules. Note
- what happens in Figure 6, where the three concatenated _DATA segments
- have different alignments. To enforce the word alignment and paragraph
- alignment of the _DATA segments in Module2 and Module3, LINK inserts one
- or more bytes of padding between the segments.
-
-
- Segment Groups
-
- A segment group establishes a logical segment address to which all offsets
- in a group of segments can refer, that is, all addresses in all segments
- in the group can be expressed as offsets relative to the segment value
- associated with the group (see Figure 7). Declaring segments in a group
- does not affect their positions in the executable file; the segments in
- a group may or may not be contiguous and can appear in any order as long
- as all address references to the group fall within 64Kb of each other.
-
- LINK reserves one group name, DGROUP, for use by Microsoft language
- translators. DGROUP is used to group compiler-generated data segments and
- a default stack segment.
-
-
- LINK Internals
-
- Many programmers use LINK as a "black box" program that transforms object
- modules into executable files. Nevertheless, it is helpful to observe
- how LINK processes object records to accomplish this task.
-
- LINK is a two-pass linker; that is, it reads all its input object modules
- twice. On Pass 1, LINK builds an address map of the segments and symbols
- in the object modules. On Pass 2, it extracts the executable code and
- program data from the object modules and builds a memory image-an exact
- replica-of the executable file.
-
- The reason LINK builds an image of the executable file in memory, instead
- of just copying code and data from object modules into the executable
- file, is that it organizes the executable file by segments and not
- according to the order in which it processes object modules. The most
- efficient way to concatenate, combine, and relocate the code and data is
- to build a map of the executable file in memory during Pass 1 and then
- fill in the map with code and data during Pass 2.
-
- In Versions 3.52 and later, whenever the /I (/INFORMATION) switch is
- specified in the command line, LINK displays status messages at the start
- of each pass and as it processes each object module. If the /M (that is,
- /MAP) switch is used in addition to the /I switch, LINK also displays the
- total length of each segment declared in the object modules. This
- information is helpful in determining how the structure of an
- executable file corresponds to the contents of the object modules
- processed by LINK.
-
-
- Pass 1
-
- During Pass 1, LINK processes the LNAMES, SEGDEF, GRPDEF, COMDEF, EXTDEF,
- and PUBDEF records in each input object module and uses the information
- in these object records to construct a symbol table and an address map of
- segments and segment groups.
-
-
- Symbol Table
-
- As each object module is processed, LINK uses the symbol table to resolve
- external references (declared in EXTDEF and COMDEF records) to public
- symbols. If LINK processes all the object files without resolving all the
- external references in the symbol table, it searches the input libraries
- for public symbols that match the unresolved external references. LINK
- continues to search each library until all the external references in the
- symbol table are resolved.
-
-
- Segments and Groups
-
- LINK processes every SEGDEF record according to the segment name, class
- name, and attributes specified in the record. LINK constructs a table of
- named segments and updates it as it concatenates or combines segments.
- This allows LINK to associate each public symbol in the symbol table with
- an offset into the segment in which the symbol is declared.
-
- LINK also generates default segments into which it places communal
- variables declared in COMDEF records. Near communal variables are placed
- in one paragraph-aligned public segment named c_common, with class name
- BSS (block storage space) and group DGROUP. Far communal variables are
- placed in a paragraph-aligned segment named FAR_BSS, with class name
- FAR_BSS. The combine type of each far communal variable's FAR_BSS segment
- is private (that is, not public, memory, common, or stack). As many
- FAR_BSS segments as necessary are generated.
-
- After all the object files have been read and all the external references
- in the symbol table have been resolved, LINK has a complete map of the
- addresses of all segments and symbols in the program. If a MAP file has
- been requested, LINK creates the file and writes the address map to it.
- Then LINK initiates Pass 2.
-
-
- Pass 2
-
- In Pass 2, LINK extracts executable code and program data from the LEDATA
- and LIDATA records in the object modules. It builds the code and data into
- a memory image of the executable file. During Pass 2, LINK also carries
- out all the address relocations and fixups related to segment relocation,
- segment grouping, and resolution of external references, as well as any
- other address fixups specified explicitly in object module FIXUPP records.
-
- If it determines during Pass 2 that not enough RAM is available to
- contain the entire image, LINK creates a temporary file in the current
- directory on the default disk drive. (LINK Version 3.60 and later use the
- environment variable TMP to find the directory for the temporary scratch
- file.) LINK then uses this file in addition to all the available RAM to
- construct the image of the executable file. (In versions of MS-DOS earlier
- than 3.0, the temporary file is named VM.TMP; in Version 3.0 and later,
- LINK uses Interrupt 21H Function 5AH to create the file.)
-
- LINK reads each of the input object modules in the same order as it did in
- Pass 1. This time it copies the information from each object module's
- LEDATA and LIDATA records into the memory image of each segment in the
- proper sequence. This is when LINK expands the iterated data in each
- LIDATA record it processes.
-
- LINK processes each LEDATA and LIDATA record along with the corresponding
- FIXUPP record, if one exists. LINK processes the FIXUPP record, performs
- the address calculations necessary for relocation, segment grouping, and
- resolving external references, and then stores binary data from the
- LEDATA or LIDATA record, including the results of the address
- calculations, in the proper segment in the memory image.
-
- The only exception to this process occurs when a FIXUPP record refers to
- a segment address. In this case, LINK adds the address of the fixup to a
- table of segment fixups; this table is used later to generate the segment
- relocation table in the EXE header.
-
- When all the data has been extracted from the object modules and all the
- fixups have been carried out, the memory image is complete. LINK now has
- all the information it needs to build the EXE header (see Figure 8). At
- this point, therefore, LINK creates the executable file and writes the
- header and all segments into it.
-
-
- Summary
-
- By using LINK to rearrange and combine segments, a programmer can
- generate an executable file in which segment order and addressing serve
- specific purposes. Careful use of LINK leads to more efficient use of
- memory and simpler, more efficient programs.
-
- LINK's characteristic support for segment ordering, for run-time memory
- management, and for dynamic overlays has an impact in many different
- situations. Programmers who write their own language translators must
- bear in mind the special conventions followed by LINK in support of
- Microsoft language translators. Application programmers must be
- familiar with LINK's capabilities when they use assembly language or
- link assembly language programs with object modules generated by
- Microsoft compilers. LINK is a powerful program development tool and
- understanding its special capabilities can lead to more efficient
- programs.
-
-
- Figure 1: A simple relocation. Both object modules contain code that LINK
- combines into one logical segment. In this example, LINK appends
- the 50H bytes of code in Module2 to the 64H bytes of code in
- Module1. LINK relocates all references to addresses in the code
- segment so that they apply to the combined segment.
-
- ╔════════════════════╗ ╔════════════════════╗
- ║ Code segment ║ ║ ║
- ║ (64H bytes) ║ ║ Code segment ║
- ║ ║ ║ (50H bytes) ║
- ║Label1 at offset 10H║ ║ ║
- ╚════════════════════╝ ╚════════════════════╝
- Module 1 Module 2
- ╔══════════════════════╗
- ║ Code segment ║
- ║ (B4H bytes) ║
- ║ ║
- ║ Label1 at offset 10H ║
- ║ Label2 at offset 74H ║
- ║ ║
- ╚══════════════════════╝
- Combined code segment
-
-
- Figure 2: Resolving an external reference. LINK resolves the external
- reference in Module1 (declared in an EXTDEF record) with the
- address of Label2 in Module2 (declared in a PUBDEF record).
-
- Module 1 Module 2
- ╔══════════════════════════╗ ╔══════════════════════════╗
- ║ Code segment ║ ║ Code segment ║
- ║ EXTDEF Label2 ║ ║ PUBDEF Label2 ║
- ║ jmp Label2 ║ ║ ║
- ║ ∙ ║ ║ Label2: ∙ ║
- ║ ∙ ║ ║ ∙ ║
- ║ ∙ ║ ║ ∙ ║
- ╚══════════════════════════╝ ╚══════════════════════════╝
- Combined code segment
- ╔═════════════════════════╗
- ║ Code segment ║
- ║ ∙ ║
- ║ ∙ ║
- ║ ∙ ║
- ║ ║
- ║ jmp Label2 ║
- ║ ∙ ║
- ║ ∙ ║
- ║ ∙ ║
- ║ ║
- ║ Label2: ∙ ║
- ║ ∙ ║
- ║ ∙ ║
- ╚═════════════════════════╝
-
-
- Figure 3: Ordered object module processing by LINK. (a) With the command
- LINK MYPROG,,,MYLIB, the production version of myfunc() in
- MYLIB.LIB is used. (b) With the command LINK MYPROG+MYFUNC,,,
- MYLIB, the debugging bersion of myfunc() in MYFUNC.OBJ is used.
-
- ┌────────────────┐ ┌─────────────────────┐
- │ Main () │ │ │
- │ { ├────│ EXTDEF for myfunc() ├──┐ ┌───────────────┐
- │ x=myfunc(y) ; │ │ │ │ │ Executable │
- │ } │ └─────────────────────┘ ├──│ code contains │
- └────────────────┘ MYPROG.OBJ │ │ myfunc() │
- MYPROG.C │ │ derived from │
- │ │ either │
- ┌────────────────┐ ┌─────────────────────┐ │ │ MUFUNC.OBJ or │
- │ myfunc(a) │ │ │ │ │ MYLIB.OBJ │
- │ int a; ├────│ PUBDEF for myfunc() ├──┤ └───────────────┘
- │ { │ │ │ │
- │ ∙ │ └─────────────────────┘ │
- │ ∙ │ MYFUNC.OBJ │
- │ ∙ │ │
- │ } │ ┌─────────────────────┐ │
- └────────────────┘ │ ∙ │ │
- MYFUNC.C │ ∙ │ │
- │ ∙ │ │
- │ PUBDEF for myfunc() ├──┘
- │ ∙ │
- │ ∙ │
- │ ∙ │
- └─────────────────────┘
- MYLIB.LIB
-
-
- Figure 4: Library search order. Modules are incorporated into the
- executable file as LINK extracts them from the libraries to
- resolve external references.
-
- Start of program─┐
- ┌─────────────┐ ┌─────────────┐ ┌────────────────┐ ┌──────────────┐┐│
- │ ModuleA │ │ ModuleC │ │ ModuleMAIN │ │ ModuleMAIN │├┘
- │ Call C │ │ Call B │ │ Call C │ ├──────────────┤┘
- ├─────────────┤ └─────────────┘ └────────────────┘ │ ModuleA │
- │ ModuleB │ LIB2.LIB MYPROG.OBJ ├──────────────┤
- │ │ │ ModuleC │
- └─────────────┘ ├──────────────┤
- LIB1.LIB │ ModuleB │
- └──────────────┘
- MYPROG.EXE
-
-
- Figure 5: Segment order and concatenation by LINK. The start of each file,
- corresponding to the lowest address, is at the top.
-
- MYPROG1.ASM
- ┌──────────────────────────────┐ MYPROG1.OBJ
- │ _TEXT SEGMENT public 'CODE' │ ┌───────────────────┐
- ├──────────────────────────────┤ │SEGDEF for_TEXT │
- │FAR_TEXT SEGMENT public 'CODE'├─│SEGDEF for FAR_TEXT├───┐
- ├──────────────────────────────┤ │SEFDEF for_DATA │ │
- │ _DATA SEGMENT public 'DATA' │ └───────────────────┘ │
- └──────────────────────────────┘ │
- │
- MYPROG2.ASM │
- ┌──────────────────────────────┐ MYPROG2.OBJ │
- │ _TEXT SEGMENT public 'CODE' │ ┌───────────────────┐ │
- ├──────────────────────────────┼─│SEGDEF for_TEXT │ │
- │FAR_TEXT SEGMENT public 'CODE'│ │SEGDEF for FAR_TEXT├───┤
- └──────────────────────────────┘ └───────────────────┘ │
- ┌─────────────────────────────────────────────────────┘
- │ MYPROG1.EXE
- │ ┌────────────┐─┐ ─┐
- │ │ │ ├─ _TEXT │
- │ │ │ │ segment │
- └──│ │ │ │
- ├────────────┤═╡ ├─ 'CODE' class
- │ │ │ │
- │ │ ├─ FAR_TEXT │
- │ │ │ segment │
- │ │ │ │
- ├────────────┤═╡ ─┘
- │ │ ├─ _DATA
- └────────────┘─┘ segment
-
-
- Figure 6: Alignment of combined segments. LINK enforces segment alignment by
- padding combined segments with unitialized data bytes.
-
- Module 1 Module 2 Module 3
- ╔═══════════════════╗ ╔════════════════════╗ ╔════════════════════╗
- ║ DATA SEGMENT byte ║ ║ DATA SEGMENTS word ║ ║ DATA SEGMENTS para ║
- ║ public 35H bytes ║ ║ public 35H bytes ║ ║ public 35H bytes ║
- ╚═══════════════════╝ ╚════════════════════╝ ╚════════════════════╝
-
- 00H┌─────────────────────────────┐─┐
- │ Module 1 │ ├─35H bytes (byte aligned)
- 35H├─────────────────────────────┤─┘
- │█████████████████████████████│
- 36H├─────────────────────────────┤─┐
- │ Module 2 │ ├─35H bytes (word aligned)
- 6BH├─────────────────────────────┤─┘
- │█████████████████████████████│
- │█████████████████████████████│
- 70H├─────────────────────────────┤─┐
- │ Module 3 │ ├─35H bytes (paragraph aligned)
- └─────────────────────────────┘─┘
- Resulting _DATA segment
- in .EXE file
-
-
- Figure 7: Example of group addressing. The first MOV loads the value 00H
- into AX (the offset of TestData relative to DataSeg2); the second
- MOV loads the value 100H into AX (the offset of TestData relative
- to the group DataGroup).
-
- DataGroup GROUP DataSeg1,DataSeg2
- CodeSeg SEGMENT byte public 'CODE'
- ASSUME cs:CodeSeg
-
- mov ax,offset DataSeg2:TestData
- mov ax,offset DataGroup:TestData
-
- CodeSeg ENDS
-
- DataSeg1 SEGMENT para public 'DATA'
- DB 100h dup(?)
- DataSeg1 ENDS
-
- DataSeg2 SEGMENT para public 'DATA'
- TestData DB ?
- DataSeg2 ENDS
- END
-
-
- Figure 8: How LINK Builds an EXE File Header
-
- ╓┌───────┌──────────────────────────────────┌────────────────────────────────
- Offset Contents Comments
-
- 00H 'MZ' EXE file signature
-
- 02H Length of executable ┐
- image MOD 512 │
- │ Total size of all segments
- 04H Length of executable image in │ plus EXE file header
- 512-byte pages, including last │
- partial page (if any) ┘
-
- 06H Number of run-time Number of segment fixups
- segment relocations
-
- 08H Size of the EXE header in Size of segment relocation table
- Offset Contents Comments
- 08H Size of the EXE header in Size of segment relocation table
- 16-byte paragraphs
-
- 0AH MINALLOC: Minimum amount Size of uninitialized data and/or
- of RAM to be allocated above stack segments at end of program
- end of the loaded program (in (0 if /HI switch is used)
- 16-byte paragraphs)
-
- 0CH MAXALLOC: Maximum amount 0 if /HI switch is used; value
- of RAM to be allocated above specified with /CP switch;
- end of the loaded program (in FFFFH if /CP and /HI switches
- 16-byte paragraphs) are not used
-
- 0EH Stack segment (initial value for Address of stack segment relative
- SS register); relocated by to start of executable image
- MS-DOS when program is loaded
-
- 10H Stack pointer (initial Size of stack segment in bytes
- value for register SP)
-
- Offset Contents Comments
- 12H Checksum One's complement of sum of all
- words in file, excluding
- checksum itself
-
- 14H Entry point offset (initial ┐
- value for register IP) │
- │ MODEND object record that
- 16H Entry point segment (initial │ specifies program start address
- value for register CS); │
- relocated by MS-DOS when │
- program is loaded ┘
-
- 18H Offset of start of segment
- relocation table relative
- to start of EXE header
-
- 1AH Overlay number 0 for resident segments;
- >0 for overlay segments
-
- Offset Contents Comments
- 1CH Reserved
-
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Vol. 3 No. 4 Table of Contents
-
- DARWIN: Merrill Lynch Develops a New Workstation Based on Windows 2.0
-
- In order to easily access data from numerous incompatible databases and
- provide a graphical interface with report writing capabilities, Merrill
- Lynch turned to the Windows development environment and created DARWIN
- (Data Access and Reporting Through Windows).
-
-
- CodeView(R) for Windows Provides an Interactive Debugging Environment
-
- An interactive debugger allows immediate interaction with an executing
- program. CodeView for Windows (CVW) is a special version of the Microsoft
- debugger that understands such Windows specific concepts as messages, the
- Windows Memory Manager and dynamic-link libraries.
-
-
- OS/2 Graphics Programming Interface: An Introduction to Coordinate Spaces
-
- Coordinate spaces, which define drawing areas, are an important component of
- the OS/2 Presentation Manager. Transforms allow the mapping of objects from
- one coordinate space to the next. Coordinate spaces under the micro-PS and
- some of the GPI functions for handling transforms are explored.
-
-
- Microsoft(R) Macro Assembler Version 5.1 Simplifies Macros and Interfacing
-
- Macro Assembler 5.1 provides significant new features that enable the
- programmer to easily develop macros and interface to high-level languages.
- Other new features include the addition of high-level constructs such as
- ELSE extensions, local labels, and type directives.
-
-
- Color Mixing Principles and How Color Works in the Raster Video Model
-
- The underlying principles of color generation are complex. Exploring some
- common color mixing models such as the RGB (Red-Green-Blue) and HSV
- (Hue-Saturation-Value) models leads to a better understanding of the methods
- employed for creating colors on PC video displays.
-
-
- Creating User-Defined Controls for Your Own Windows Applications
-
- The ability to define your own controls functionally extends the Windows
- user interface, giving your applications greater visual appeal and the user
- greater program control. Palette, a color mixing program, utilizes several
- such controls, as provided in a program called Spectrum.
-
-
- SQL Server Brings Distributed DBMS Technology to OS/2 Via LAN Manager
-
- SQL Server, in combination with the OS/2 LAN Manager, brings mainframe
- and minicomputer power to networked personal computers. It adds stored
- procedures, triggers, and an advanced transaction-oriented kernel,
- essential features for assuring data integrity, to SQL.
-
-
- Ask Dr. Bob
-
-
- EDITOR'S NOTE
-
- What does OS/2 mean for the professional programmer? For one thing, it means
- new ways of thinking about how programs are developed. By itself,
- multitasking is a boon to programmers; it's a real time-saver to have an
- editor, compiler, and debugger all running concurrently.
-
- Debugging under OS/2 with the Presentation Manager (PM) offers some new
- alternatives that just weren't feasible under DOS. In the last issue we
- presented a debugging utility for Windows applications consisting of about
- 600 lines of code. Building an equivalent debugging tool under Presentation
- Manager is a trivial task.
-
- Let's assume that you have at your disposal a UNIX(R)-like grep function
- that will locate whatever strings you might be looking for. Within your PM
- application, whenever you want to generate debugging information you simply
- include a printf statement, for example:
-
- printf('17 BeginPaint returned 0x%04x\n', usResult);
-
- You would then start your PM application from a windowed instance of
- CMD.EXE:
-
- start myPMapp | grep '.*'
-
- which would bring up the PM application in a window, while the printf output
- ends up being piped to the instance of grep running in the CMD.EXE window.
- With grep, you could, for example, filter out everything but type 17
- messages simply by starting the PM application as follows:
-
- start myPMapp | grep '^17'
-
- The appropriate messages would then be filtered out. Of course, you would
- need a much more elaborate OS/2 VIO-based filter for significantly greater
- flexibility.
-
- The multitasking/windowing facilities available under OS/2 and Presentation
- Manager present programmers with a far richer set of options, in many cases
- making formerly complex tasks much simpler. The successful OS/2 programmer
- will be the one with the vision to put these new advantages to work.──Ed.
-
- Masthead
-
- JONATHAN D. LAZARUS
- Editor and Publisher
-
- EDITORIAL
-
- TONY RIZZO
- Technical Editor
-
- KAREN STRAUSS
- Assistant Editor
-
- JOANNE STEINHART
- Production Editor
-
- GERALD CARNEY
- Staff Editor
-
- KIM HOROWITZ
- Editorial Assistant
-
- ART
-
- MICHAEL LONGACRE
- Art Director
-
- VALERIE MYERS
- Associate Art Director
-
- CIRCULATION
-
- WILLIAM B. GRANBERG
- Circulation Manager
-
- L. PERRIN TOMICH
- Assistant to the Publisher
-
- DONNA PUIZINA
- Administrative Assistant
-
- Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
- in part or in whole without permission is prohibited.
-
- Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
- NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
- Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
- President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
- William Neukom, Secretary.
-
- Microsoft Corporation assumes no liability for any damages resulting from
- the use of the information contained herein.
-
- Microsoft, MS, and CodeView are registered trademarks of Microsoft
- Corporation. CompuServe is a registered trademark of CompuServe, Inc. dBASE,
- dBASE III, and dBASE III PLUS are registered trademarks of Ashton-Tate
- Corporation. EtherNet is a registered trademark of Xerox Corporation. IBM is
- a registered trademark of International Business Machines Corporation.
- Token-Ring is a trademark of International Business Machines Corporation.
- Intel is a registered trademark of Intel Corporation. Above is a trademark
- of Intel Corporation. AST and RAMpage! are registered trademarks of AST
- Research, Inc. SQLBase is a registered trademark of Gupta Technologies, Inc.
- UNIX is a registered trademark of American Telephone and Telegraph
- Company.
-
- ████████████████████████████████████████████████████████████████████████████
-
- DARWIN: Merrill Lynch Develops a New Workstation Based on Windows 2.03
-
- Tony Rizzo and Karen Strauss
-
- What do you do when you need information from a wide variety of
- databases, each with its own access method, and you want to ensure vendor
- independence in workstation hardware and applications software, but you
- also want to provide a common, graphics-oriented user interface with
- report-writing capabilities? You turn to the Windows development
- environment and write a snappy application that uses all of the tools
- that Windows puts at your disposal.
-
- Corporate MIS, a relatively new division of Merrill Lynch & Co., Inc., is
- responsible for providing headquarters management with financial control
- and human resources information. In the past, this information has resided
- in many separate database systems, each with its own access method,
- usually incompatible with the others. Because of the difficulties in
- combining them, about a year ago Corporate MIS began considering new
- ways of accessing data through personal computers.
-
- A key issue for Corporate MIS was the development of a strategy to allow
- different programming teams within the division to work independently
- of each other, yet adhere to common design specifications. For example,
- the database design group would work separately from the information
- presentation group, but each would develop applications to be
- compatible and consistent across all groups.
-
- Corporate MIS came up with a plan to base all future organization of its
- data on the relational model and use Structured Query Language (SQL) as
- the means of accessing the data. Its database designers would be free to
- select the relational technology best suited to any given task. The
- database engine could be based on a LAN, a mainframe, or a database
- machine──the only requirement would be that the technology had to be able
- to execute "standard" SQL queries.
-
- Workstation designers directly concerned with how data is presented on a
- PC to the end user would only have to deal with generating standard SQL
- requests for data and handling data returned to the workstation from the
- database engine.
-
- On the user side, the Corporate MIS plan required that workstation
- designers use a common interactive graphical interface. With a PC-based
- graphical interface a designer no longer had to be concerned with
- building applications that rely, for example, on asynch or 3270-emulation
- windows. Also, Corporate MIS wanted to provide users with a common
- report writer facility to simplify formatting data.
-
- This plan envisioned a common development tool that any workstation
- designer could incorporate into an application whenever that application
- had to access and format data. It was at this point that Corporate MIS
- turned to its PC Software Development group. "Our charter was to design
- a method for querying SQL databases using icons or pictures of what the
- user wanted to do," says Anthony Pizi, an assistant vice president within
- Corporate MIS responsible for PC software development.
-
- Pizi decided to use the Microsoft(R) Windows Version 2.03 environment as
- his tool's common graphical user interface; after all, Microsoft Excel,
- with its integrated, Windows-based interface, had been an instant success
- with Merrill Lynch analysts. "This was our first major development effort
- using Windows, so the project took on a subtheme demonstrating that
- Windows really could deliver the graphical interface and software
- integration that we needed," says Pizi. "And so far, so good."
-
- As the software begins to take shape, according to Pizi, Merrill Lynch is
- simultaneously reorganizing its PC hardware, under which most PCs will
- eventually be connected to various LAN systems, such as EtherNet(R) and
- Token-Ring(TM).
-
-
- Defining the System
-
- Pizi had to implement and integrate three specific functions into his
- design.
-
- First, a front-end system of menu options, dialog boxes, and pop-ups would
- let a user formulate complex requests by simply pointing and clicking on
- various fields and commands with a mouse. Second, the application had to
- be able to take requests formulated by the user, translate them into SQL
- requests, send them to the appropriate database subsystem, and handle
- the returned data. Finally, the application required the ability to
- format data easily in any manner.
-
- This set of tools would not be tied to any specific database. The original
- model might, for example, be based on Gupta Technologies' SQLBase(R), but
- any engine could later be integrated into the system.
-
- Furthermore, the entire application would be written to be NetBIOS-
- compatible, permitting it to run on any workstation attached to any LAN
- supporting NetBIOS. The workstation/LAN environment would be hooked into a
- SQL Network Gateway, providing links to such mainframe databases as
- Merrill Lynch's IBM(R) DB2 system.
-
- For end users the resulting applications and workstations would provide:
-
- ■ consistent hardware configurations
- ■ a consistent user interface
- ■ remote access to many corporate databases
- ■ a simple means of generating SQL queries
- ■ substantial report writer capabilities
-
- These new systems would improve Corporate MIS's ability to leverage
- existing systems and human resources, offering:
-
- ■ reduced costs
- ■ increased productivity
- ■ improved information flow
- ■ enhanced and more informed decision making
- ■ better control of the PC environment
-
-
- Building DARWIN
-
- Pizi's group started to experiment with the Windows development
- environment and to develop the strategies for communicating with
- remote back-end database systems. Once the group had demonstrated their
- ability to talk with a database through various front-end models they put
- together under Windows, Pizi felt they were ready to begin.
-
- The design for Data Access and Reporting Through Windows (DARWIN) was
- turned over to Steve Canny and Brian Hayden, two senior
- programmer/analysts. "On December 15 of last year the first line of
- DARWIN code was written," says Pizi. Five months later DARWIN was
- virtually complete.
-
- As Canny tells it, "In the beginning, building DARWIN was a classic
- example of design on the fly. Neither Brian [Hayden] nor I were Windows
- masters. We would see some portion of what needed to be done and go off
- and see if we could get Windows to do it. It was kind of like a jigsaw
- puzzle, where you find pieces one at a time. Occasionally two or three
- pieces would fall into place together.
-
- "One of us would come up with the basics of an idea, we'd discuss it, then
- implement it in the next version of the software. Since I had the most
- experience with graphics programming, I developed the user interface.
- Brian knew the database end of things and designed the link to the
- database server. Brian also developed the code for all of the DARWIN
- dialog boxes that present data to the user. Eventually, we were able to
- get the entire thing to compile, and the beta version of DARWIN was born."
-
-
- DARWIN and Microsoft Excel
-
- Originally, DARWIN was to be responsible for displaying and printing the
- query results. "We had managed to put together a low-level stub for
- handling the display and were sweating about the thought of writing a
- display and print manager that had reasonable features," Canny says.
-
- "Fortunately, it was just about then that Tony [Pizi] came in shaking his
- hands over his head and talking fast, the way he usually does when he's
- had a particularly good brainstorming session. He told us about his idea
- to send DARWIN's output directly to Microsoft Excel, and we told him he
- was crazy. Two days later we began to think that maybe it wasn't so crazy.
- Then we became absolutely convinced that sending the output directly to
- Microsoft Excel was the way to go," says Canny.
-
- A user goes into DARWIN, builds a query, and the data goes directly into a
- Microsoft Excel spreadsheet. This idea resulted in a substantial
- breakthrough, and the third tool for which DARWIN was responsible began to
- fall into place. The design changed so that Microsoft Excel became, in a
- sense, a part of DARWIN; the spreadsheet would serve as DARWIN's report
- writer (see Figure 1) and would, in fact, be responsible for bringing up
- DARWIN itself.
-
-
- Using DARWIN
-
- To bring up DARWIN, the user executes a Microsoft Excel macro, which
- supplies DARWIN with its menu bar and hides Microsoft Excel. Once DARWIN
- is running, the macro controls whether the user is in DARWIN itself or in
- Microsoft Excel by monitoring the appropriate menu option provided in
- the two applications. The user doesn't even have to know how to start
- Microsoft Excel or run a macro if a program such as the hDC Computer
- Corporation ClickStart applications organizer is used──simply clicking on
- an icon will start DARWIN.
-
- DARWIN establishes the underlying connections to the required database.
- The database can be a server on a network, or it can be a mainframe
- database that is linked to via a SQL Network Gateway (see Figure 2).
- Gupta's SQLBase supplies the necessary software to make this mainframe
- link look like just another local network-based server to DARWIN.
-
- DARWIN offers the user database choices. Tables of data are grouped
- together as categories; each category consists of preestablished
- collections and groupings of data tables. The user first selects the
- desired database category. Once a selection is made, DARWIN provides a
- list of the fields in each table and menus of tools for extracting the
- required data (see Figures 3, 4, and 5).
-
- DARWIN's client area, which is an iconic representation of the query
- being assembled, is divided into two child windows. The top window
- containing the short horizontal scroll bar is the Choice List window; the
- other is the Database Context window.
-
- The Choice List area corresponds to the SQL SELECT clause; it displays
- the columns that will make up the result table, that is, the information
- for which the database is being queried. The Database Context window
- corresponds to the FROM and WHERE clauses; it shows the tables from which
- the selected columns come and the constraints and joins that are detailed
- in the WHERE clause. [For more information on SQL see "SQL: A Short
- Primer" in this issue──Ed.]
-
- To the user of the DARWIN query tool, the tables of data are represented
- on the screen as boxes, and they consist of one or more columns represented
- on the screen as rows, not to be confused with rows of data. These boxes are
- simple child windows (windows without scroll bars, menu bars, and so on)
- that can be collapsed by clicking on the minimize arrow and dragged around
- the client area with a mouse.
-
-
- Creating Joins
-
- Joins are created by clicking the left mouse button on the first data
- item, then clicking the right mouse button on a second data item in
- another table. The left-right button combinations are analogous to setting
- up a "from (the left button) ... to (the right button)" relationship.
- After the right button is clicked DARWIN draws a line from one table to
- another (see Figure 6). The join that is formed by default is an equi-
- join, the most commonly used join.
-
- The type of join created is easily modified by the user (see Figure 7).
- Join constraints are established by clicking on an appropriate column and
- using the dialog box that is then presented. Several constraint options,
- such as the mathematical symbols for greater than, less than, less than
- or equal to, and so on, are offered. The user establishes data constraints
- through a similar process (see Figure 8).
-
- The user is thus able to formulate multitable queries based on joins
- made only through mouse manipulations and menu choices presented by
- DARWIN. With a visual summary of database tables displayed on screen,
- users can easily discover relationships between collections of data that
- they didn't realize they had access to. The user is provided with all the
- information and tools necessary to build a detailed query, based on
- standard Windows objects: windows, listboxes, and pull-down/pop-up
- menus. Figure 9 shows the resulting graphical representation of a join
- with a data constraint.
-
- DARWIN takes the user-supplied information, which is stored in a single
- dynamic local data structure, and creates a standard SQL query. Once the
- actual SQL statements are created, the user clicks on the RUN! command,
- and DARWIN does the rest, going out to the necessary tables, collecting
- the data, and writing it out to a CE_BIFF file, which is the Microsoft
- Excel file format.
-
- When DARWIN indicates that a query has been completed and a result table
- thus created, the user simply toggles back to Microsoft Excel by clicking
- on the View Output option under the Window pull-down menu (see Figure 10)
- and finds the data, supplied through the CE_BIFF file that has just been
- created, waiting in a standard Microsoft Excel spreadsheet (see Figure
- 11). According to Pizi, because Microsoft Excel was designed with the
- capacity to handle database information, the spreadsheet already had some
- of the most important tools for becoming a report writer.
-
- Data can be modified and formatted and statistics generated by using all
- of Microsoft Excel's spreadsheet, formatting, and charting features. The
- user can even click on the A1 cell, and Microsoft Excel will return
- information related to the underlying SQL request made in DARWIN (see
- Figure 12).
-
- The user can also view any actual SQL query statement that is in the
- process of being built, and experienced users are even able to modify the
- query through a feature called SQL EDIT (see Figures 13 and 14). At this
- point, according to Pizi, more complex nested queries are not yet
- supported, but a knowledgeable user can build more complex requests
- through SQL EDIT.
-
-
- Tight Integration
-
- Microsoft Excel and DARWIN are very tightly integrated. "We like people
- to ask, 'Are you in DARWIN or Microsoft Excel?'" Hayden says. "It is so
- seamless, it is hard to tell where one ends and the other begins [see
- Figure 15]. Basically, when the table screens are up, you are in DARWIN.
- When you have the data you see in the report, you are in Microsoft Excel."
-
- Hayden credits Microsoft Excel for this now-you-see-it, now-you-don't
- quality. "Microsoft Excel has an incredibly rich program language and allows
- you to use Windows function calls via macros that make Windows environment
- calls. The macro we've developed in Microsoft Excel maximizes an
- application, in our case DARWIN, and adds DARWIN's menu bar," he says.
-
- "The command is 'minimize Excel, maximize DARWIN,' so DARWIN comes up on
- the screen. Microsoft Excel is the landing pad for the data DARWIN
- provides," says Hayden. When the user wants to view the data in Microsoft
- Excel, the process is reversed. Figure 16 shows an early version of the
- macro; a close inspection of the macro demonstrates how simple it is to go
- from the spreadsheet to DARWIN and back.
-
-
- Powerful Environment
-
- The power resulting from the integration of DARWIN and Microsoft Excel was
- not immediately obvious to Canny and Hayden. Canny says, "When we first
- explored Microsoft Excel as a possibility, it took a while to assimilate
- the technology. Over time, as we better understood the environment we
- realized what a really great opportunity there was for developing a unique
- application."
-
- For example, the format in which data returned by DARWIN would be
- represented required much thought, and various scenarios were drawn up.
- Canny says, "At first, there was a good deal of experimentation. We didn't
- have all the specs on Microsoft Excel and were waiting around for that.
- The CE_BIFF format was not our first choice; we arrived at it after trying
- a number of different alternatives. Our first attempt was to have DARWIN
- write out a simple comma-delimited ASCII text file.
-
- "But then the Microsoft Excel development people clued us in to the
- CE_BIFF format. The availability of different fonts and the ability to set
- results in a single cell, as well as the accuracy of the CE_BIFF format,
- made it immediately apparent that CE_BIFF was the way to go. It was
- straightforward and well documented by Microsoft. With two or three days
- of research and two or three days of coding, we had it working."
-
- Canny now considers Microsoft Excel to be a cooperative environment
- because it can be used as a Windows programming tool or an adjunct. "You
- write programs for things that Microsoft Excel does not specialize in, and
- you send to Microsoft Excel the jobs it does specialize in," he says.
- "People want calculations, editing, and graphs to make it nice for the
- boss. Microsoft Excel already does this very well, and we didn't see
- reinventing those tasks in DARWIN."
-
- For all its strengths, the Microsoft Excel environment has its
- frustrations as well, Hayden says. "Microsoft Excel appears to break
- Microsoft's own recommended standard that you never hog memory, that the
- application give it up as soon as it finished. Microsoft Excel goes out
- and asks, 'How much memory is here? Give me all of it.' It is breaking
- the friendliness standards."
-
- In contrast to Microsoft Excel, which uses all the available memory,
- DARWIN uses about 175Kb of memory. It takes up the most memory (just over
- 300Kb) when you are using the constraint dialog box, the pull-down menu
- that lets users set parameters on their queries.
-
-
- Windows Challenges
-
- Hayden says he felt challenged by the need to reconcile himself to the
- device-independent nature of the Windows environment. Canny concurs:
- "Windows is a very different programming environment from the old
- sequential model, in which program control flow goes to the first function,
- the second function, the third function, goes back to the second function,
- and finishes. With Windows, you have a series of independent, often
- asynchronous processes, triggered by another process. That is a very new
- concept for most people who work in the field." Hayden adds, "You've got to
- follow the modular specs. If you start playing tricks, you're dead."
-
- Hayden feels that Windows is an important step forward in application
- development. "Windows is like C at the very beginning: a beautiful
- language, but with no libraries. That means initially developing an
- application is very difficult. You have to rebuild repetitive routines,
- although people are starting to release some Windows libraries to make
- things easier. But once you understand it, Windows is really good. It is a
- liberating mindset."
-
-
- SQL
-
- The design team appreciates SQL because of the rigorous mathematical
- underpinning established by E.F. Codd in his original paper on relational
- databases ("A Relational Model of Data for Shared Data Banks,"
- Communications of the ACM 13, No. 6, June 1970). According to Pizi, "This
- mathematical basis is our assurance that a database designed with the
- techniques of normalization will serve the emerging and changing needs
- of the organization in the future. It gives us a firm foundation on which
- to build."
-
- This appreciation of SQL is reflected in some of the design considerations
- that went into DARWIN. As Pizi points out, he wanted to remain true to what
- SQL actually is, namely a rigorously defined query language. Therefore, when
- DARWIN generates a SQL query, it creates a pure set of SQL statements that
- have no other purpose than to go out and extract data.
-
- DARWIN itself has no ability to format data or set up column headings; a
- number of the current implementations of SQL include user shells to
- handle such things. Pizi feels that this is a report writer issue, the
- execution of which should be handled locally at the user's "personal"
- machine. According to Pizi, "An IBM 3090, for example, should not be
- spending any of its time formatting data and worrying about where to put
- it; you shouldn't use the mainframe to manipulate the cursor." This
- reflects the design team's view of what distributed processing is all
- about.
-
- This approach isolates DARWIN from the data it is working with, which
- could come from any database. According to Hayden, "DARWIN itself doesn't
- know anything about the database." This isolation allows Corporate MIS's
- database designers to use any database technology they want.
-
-
- SQL API and DDE
-
- DARWIN speaks to a SQL Applications Programming Interface (SQL API) that
- Hayden wrote. The interface translates DARWIN's query output into whatever
- vendor-specific APIs may be out there. From the workstation designer's
- perspective, DARWIN will become a tool that offers the designer a virtual
- SQL interface to all of Merrill Lynch's relational database subsystems.
-
- According to Pizi, eventually "Windows' Dynamic Data Exchange [DDE]
- facility will be the pipeline to these APIs." Says Hayden, "DDE fits in
- where DARWIN talks to another application that is doing the database
- querying, in this case the SQL API. In the future, when we go to different
- databases simultaneously, DARWIN as a program does not have to be
- re-released." The SQL API will simply be modified as necessary for each
- different database. It sits on one end of DARWIN, and DDE will handle the
- interface between the two.
-
- "Since DARWIN is growing into a large and complex system with more than
- 10,000 lines of code, the question of managing complexity has to be
- raised," says Canny. According to Pizi, the current design falls into
- several categories: 48 percent of the code is devoted to Windows
- overhead, 32 percent makes up the SQL API, 18 percent is dedicated to
- graphics, and 2 percent of the code deals with creating CE_BIFF files.
-
- The one piece of advice that Canny has for other Windows developers is to
- "make sure that all Windows code is built in a modular fashion. With
- DARWIN, we need to be able to make additions to it without upsetting what
- already exists. The program needs to be organized into separate modules,
- with simple input and simple output that is well defined."
-
- The SQL API is an example of this design goal. "Our SQL API isn't really
- part of Windows," said Hayden. "It is more like a device driver. It breaks
- DARWIN off from the database." Based on the SQL library of Gupta's LAN-
- based SQLBase product, the core of the SQL API is founded on seven "shell"
- functions, which are shown in Figure 17.
-
- According to Pizi, this ability to insulate DARWIN from the particular SQL
- engine is almost as important as DARWIN's point-and-click queries. It
- relieves the database administrator of the need to support multiple
- proprietary report writers, which are typically tied to specific database
- engines. Most importantly, everything conforms to only one standard.
-
- The DARWIN-Microsoft Excel combination anticipates in some ways the
- integration promised by the MS(R) OS/2 SQL Server and the IBM OS/2 Extended
- Edition. The Merrill Lynch developers say they will enhance the SQL API,
- which now interfaces to Gupta Technologies' SQLBase engine, to support
- SQL Server and the IBM product as they become available. They expect most
- leading database vendors to respond to user demand for a standardized
- interface between SQL engines and front-end user interfaces.
-
-
- Virtual Data Dictionary
-
- In order to communicate with any given database, DARWIN must understand
- that system's data dictionary. DARWIN also needs certain information that
- existing data dictionaries do not provide, such as printname, defined
- column widths, and standard data types. "Our virtual dictionary is a
- group of seven tables that supplement all the other data dictionaries. All
- DARWIN requires is that these seven tables be present in those data
- dictionaries," says Canny. If the tables are present in a given data
- dictionary, DARWIN can communicate with them (see Figure 18 for an
- example of one of these tables).
-
-
- What DARWIN Really Is
-
- As Pizi puts it, DARWIN is another tool that the application programmer
- will add to his or her collection of development tools. It simplifies the
- designer's work because the interface to the data and the user are already
- in place. It frees the designer to spend more time dealing with what the
- end user of an application actually needs and provides a standard means
- for creating a design.
-
- But what about end users? "It's not completely clear whether DARWIN is a
- tool for end users or application developers. It falls somewhere in
- between," Pizi says. He does not expect DARWIN to be released as a
- standalone product, though he knows that plenty of people will ask for it.
- However, Pizi does think that users will eventually push the product
- further ahead with their own Microsoft Excel macros. "There are rich
- possibilities here that we intend to explore," he says. Even Microsoft
- Excel's developers were excited when they saw a demonstration of DARWIN
- working with the spreadsheet, and now they closely follow the Merrill
- Lynch product's development, according to Pizi. "We're taking a tool
- that they produced and actually using it the way they intended."
-
- Canny and Hayden say DARWIN is just a first step toward Merrill Lynch's
- goal of vendor independence in both workstation hardware and application
- software by moving the company's development platform to a common,
- stable, graphics-oriented user interface: Windows 2.03 and Windows/386
- today, OS/2 Presentation Manager in the future. In fact, Pizi and his
- team eagerly await the official release of the Presentation Manager. They
- look forward to its rich set of graphical tools and especially to the
- freedom from the DOS memory limitations that OS/2 will offer.
-
- Despite the somewhat steep learning curve, DARWIN is now falling into
- place, and future development looks promising. As Canny says, "We are now
- in the process of sizing up what we have, cleaning it up, and applying a
- more structured approach. We don't move forward as quickly, but we no
- longer step backward either. Also, this approach allows us to be more
- predictable with our releases. This is very important now that we've
- gotten management's attention; they ask us for promises and expect us to
- deliver on them. We've made the transition from a 'no questions asked'
- R&D group to a more traditional and responsible systems development
- group." Concludes Pizi, "We're producing real tools. This isn't a toy
- any longer."
-
-
- Figure 1: DARWIN acts as an intermediary, or "front end" between Merrill
- Lynch's SQL databases and the analysis and reporting applications
- of its users.
-
- ┌───────────────┐ ┌───────────────────────────────────┐
- │▓▓▓▓▓▒▒▒▒▒░░░░░│█ │─██────────────Darwin────────────│█
- │▓▓▓ Merrill ░░│█─────Queries──────┤┌────────────────────────────────┐│█
- │▓▓▓ Lynch ░░│█ ││ DARWIN │▒│█
- │▓▓▓ Database ░░│█ ││ │▒│█
- │▓▓▓ Universe ░░│█ ││ Data Acces and Reporting │▒│█
- │▓▓▓▓▓▒▒▒▒▒░░░░░│█──Requested Data──││ Through Windows │▒│█
- └───────────────┘█ ││ │▒│█
- ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │└────────────────────────────────┘│█
- │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
- └───────────────────────────────────┘█
- ┌───────────────────────────────────┐ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
- ├─██───────Microsoft Excel────────│█ Formatted
- │┌──┬─────┬─────┬─────┬─────┬─────┐│█ Output
- │├──┼─────┼┌─────────────┐──┼─────┤▒│█─────────────────┘
- │├──┼─────┼│ Microsoft │──┼─────┤▒│█
- │├──┼─────┼│ Excel │──┼─────┤▒│█
- │├──┼─────┼└─────────────┘──┼─────┤▒│█
- │├──┼─────┼─────┼─────┼─────┼─────┤▒│█──User Formatted Reports──
- │└──┴─────┴─────┴─────┴─────┴─────┘│█
- │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
- └───────────────────────────────────┘█
- ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-
-
- Figure 2: A detailed view of Merril Lynch's proposed front-end/back-end
- interconnections.
-
- Host Computer
- ┌─────────────────────────────┐
- │ MVS │
- ├─────┬────────┬─────┬────────┤
- │ │Teradata│ │ CICS │
- │ TSO │Director│ DB2 ├────────┤
- │ │Program │ │SQL Host│
- └──┬──┴───┬────┴──┬──┴─┬───┬──┘
- │ │ └────┘ └─────────────────────LU 6.2 APPC/PC────┐
- │ └─────────────────────────────────────────────┐ │
- └──────────────────────────────────┐ │ │
- ┌────────────────┐ ┌───────────┐ ┌───────────┐ │
- ┌───┤ Communications │ │ Interface │ │ Interface │ │
- │ │ Processor │ │ Processors │ │ Processors │ │
- │ └───────┬────────┘ └─────┬──────┘ └─────┬──────┘ │
- │ │ │ │ │
- │ ┌───────┴───────────────────┴─────────────────┴──────┐ │
- │ │ Ynet │ ≈
- │ └───┬──────────────┬──────────────┬──────────────┬───┘ │
- │ │ │ │ │ │
- │ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ │
- │ │ Access │ │ Access │ │ Access │ │ Access │ │
- │ │ Module │ │ Module │ │ Module │ │ Module │ │
- │ │ Processor │ │ Processor │ │ Processor │ │ Processor │ │
- │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
- │ │ │ │ │ │
- │ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ │
- │ ▓▒░Data░▒▓ ▓▒░Data░▒▓ ▓▒░Data░▒▓ ▓▒░Data░▒▓ │
- │ ▓▒Storage▓ ▓▒Storage▓ ▓▒Storage▓ ▓▒Storage▓ │
- │ ▓▒░Units▒▓ ▓▒░Units▒▓ ▓▒░Units▒▓ ▓▒░Units▒▓ ┌─┴───────┐
- │ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ ▓▒░░░░░░▒▓ │ SQL │
- │ │ Network │
- TCP/IP │ Gateway │
- OSI ┌─────────────┐ └─┬───────┘
- XNS (June │ DARWIN │ │
- 1988) │ Workstation │ │
- ┌────┴───────┐ └──────┬──────┘ │
- │ Function ├────────┬──────┴─────NetBIOS LAN──────┬─────────────┴─┐
- │ Call │ ┌──────┴──────┐┌──────┴──────┐┌──────┴──────┐ ┌─────┴────┐
- │ Translator │ │ DARWIN ││ DARWIN ││ DARWIN │ │ SQL │
- └────────────┘ │ Workstation ││ Workstation ││ Workstation │ │ Database │
- └─────────────┘└─────────────┘└─────────────┘ │ Server │
- └──────────┘
-
-
- Figure 15: DARWIN: Top Level Dataflow Diagram
-
- ┌──────────────┐
- │ Standard SQL │
- │ Text Queries │
- └┬────────────┘
- ┌───────────────┐ │ │ ┌───────────────────────────────────┐
- │▓▓▓▓▓▒▒▒▒▒░░░░░───┘ └───┼─██────────────Darwin────────────│█
- │▓▓▓ Merrill ░░│█ │┌────────────────────────────────┐│█
- │▓▓▓ Lynch ░░│█──────────────────┤│ DARWIN │▒│█
- │▓▓▓ Database ░░│█ NetBIOS Network ││ │▒│█
- │▓▓▓ Universe ░░│█──────────────────││ Data Acces and Reporting │▒│█
- │▓▓▓▓▓▒▒▒▒▒░░░░░│█ ││ Through Windows │▒│█
- └───────────────┘█ ┌────────────│ │▒│█
- ▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀ │ │└────────────────────────────────┘│█
- ┌──────────┴┐ ┌───┤ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
- │ Result │ │ └─────────────────────────────────┘█
- │ Table │ │ ▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀│▀│▀▀▀
- └────────────┘ │ Mouse and Graphical │ │
- ┌─────┴─┐ Keyboard Input Display │ │
- │Results│ ╔═╧══════════════╗ │ │ │
- ┌───┤ Table │ ║ ░░░░░░░░░░░░░░ ─────┘ │ │
- │ └───────┘ ║ ░░░░░░░░░░░░░░ ║█ Control
- │ ║ ░░░░░░░░░░░░░░ ║█ │ │
- ▓▓▓▒░░░░▒▓▓▓ ║ ░░░░░░░░░░░░░░ ║█ Messages
- ▓▓▓▒░░░░░▒▓▓▓ ╔═╝────────────────╚═╗ │ │
- ▓▓▓CE_BIFF▓▓▓ ║ ║█ │ │
- ▓▓▓ Files ▓▓▓ ╚═══════════════════╝█ │ │
- ▓▓▓▒░░░░░▒▓▓▓ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀│▀▀▀▀ │ │
- ▓▓▓▒░░░░░▒▓▓▓ User │ │ │ │
- │ │ │ │ │
- │ │ │ │ │
- │ ┌────────────────┴───────────────┴┐
- │ ├─██───────Microsoft Excel────────│█
- ┌───────────┐ └────────────┌──┬─────┬─────┬─────┬─────┬─────┐│█
- │≡≡≡≡≡≡≡≡≡≡≡├┐ │├──┼─────┼┌─────────────┐──┼─────┤▒│█
- │≡≡≡≡≡≡≡≡≡≡≡│├┐ │├──┼─────┼│ Microsoft │──┼─────┤▒│█
- │≡≡≡≡≡≡≡≡≡≡≡│││ │├──┼─────┼│ Excel │──┼─────┤▒│█
- │≡≡≡≡≡≡≡≡≡≡≡│││─User Formatted──┤├──┼─────┼└─────────────┘──┼─────┤▒│█
- │≡≡≡≡≡≡≡≡≡≡≡│││ Reports │├──┼─────┼─────┼─────┼─────┼─────┤▒│█
- │≡≡≡≡≡≡≡≡≡≡≡│││ │└──┴─────┴─────┴─────┴─────┴─────┘│█
- └┬──────────┘││ │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
- └┬──────────┘│ └───────────────────────────────────┘█
- └───────────┘ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-
-
- Figure 16: DARWIN.XLM
-
- ╓┌───┌───────────────────────────────────────────────────────────────────────
- A
- 1 DARWIN Macro
- 2 PC Software Development
- 3
- 4
- 5
- 6 =APP.MAXIMIZE()
- 7 =ECHO(FALSE)
- 8 =ADD.MENU(1,B6:C8)
- 9 =ADD.MENU(2,B6:C8)
- 10 =ADD.MENU(3,B6:C8)
- 11 =ADD.MENU(4,B6:C8)
- 12 =ADD.MENU(5,B6:C8)
- 13 =ADD.MENU(6,B6:C8)
- 14 =EXEC("C:\WINDOWS\DARWIN\DARWIN.EXE",3
- 15 =HIDE()
- 16 =RETURN()
- 17
- 18 OUTPUT
- 19 =ECHO(FALSE)
- 20 =IF(GET.DOCUMENU(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(A29),GOTO(A21))
- A
- 20 =IF(GET.DOCUMENU(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(A29),GOTO(A21))
- 21 =DIRECTORY("C:\WINDOWS\DARWIN")
- 22 =OPEN("UNNAMED.XLS")
- 23 =COLUMN WIDTH(22,"C1")
- 24 =COLUMN WIDTH(22,"C2")
- 25 =COLUMN WIDTH(22,"C3")
- 26 =COLUMN WIDTH(22,"C4")
- 27 =DISPLAY(FALSE,FALSE,TRUE,TRUE,0)
- 28 =FULL(TRUE)
- 29 =RETURN()
- 30
- 31 LAYOUT
- 32 =IF(GET.DOCUMENT(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(CLOSE),GOTO(DARWIN))
- 33 =APP.ACTIVATE("DARWIN",FALSE)
- 34 =SEND.KEYS("%{F10}",TRUE)
- 35 =APP.MINIMIZE()
- 36 =RETURN()
- 37
- 38
- 39
- A
- 39
- 40 CLOSE
- 41 =ACTIVATE("UNNAMED.XLS")
- 42 =CLOSE(FALSE)
- 43 =APP.ACTIVATE("DARWIN",FALSE)
- 44 =SEND.KEYS("%{F10}",TRUE)
- 45 =APP.MINIMIZE()
- 46 =RETURN()
-
-
-
- Figure 17: DARWIN SQLBase Interface Shell Functions
-
- 1
-
- BOOL SQLExecute(string, bindstring);
-
- LPSTR string; /* null terminated SQL statement */
- LPSTR bindstring; /* null terminated bind data */
-
- Returns:
-
- TRUE if completed correctly.
- FALSE if error.
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- The bindstring may be NULL, indicating that there is no bind value.
- ───────────────────────────────────────────────────────────────────────────
-
- 2
-
- int SQLFetchNextRow();
-
- Returns:
-
- > 0 : number of fields in the row
- = 0 : no more information to fetch (past EOF)
- = ──1 : ERROR occurred
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- This routine fetches the next logical row from the database into a SQL
- API internal structure for later use by SQLGetField.
- ───────────────────────────────────────────────────────────────────────────
-
- 3
-
- LPSTR SQLGetField(fieldnumber, datatype, flag);
-
- int fieldnumber; /* desired field (first field=1) */
- LPINT datatype; /* datadictionary/ metaphore datatypes */
- BOOL flag; /* TRUE=remove leading & trailing blanks */
-
- Returns:
-
- LPSTR, which must be cast to the correct type for usage.
- NULL if field is null.
-
- ───────────────────────────────────────────────────────────────────────────
- Notes:
- Setting flag = TRUE is suggested because SQLBase pads fields to maximum
- field width.
-
- If returned data type =
-
- string -> cast as LPSTR
- long -> cast as LPLONG
- date -> cast as LPDOUBLE
- double-> cast as LPDOUBLE
-
- typedef double FAR *LPDOUBLE /* defined in SQLAPI.H */
- typedef long int FAR *LPLONG /* defined in SQLAPI.H */
- ───────────────────────────────────────────────────────────────────────────
-
- 4
-
- LPSTR SQLGetFieldAsText(fieldnumber, datatype, flag);
-
- Returns:
-
- LPSTR to a NULL-terminated string.
- NULL if field is null.
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- Setting flag = TRUE is suggested because SQLBase pads fields to maximum
- field width. Numericals are padded to 27 digits in ASCII representation.
- ───────────────────────────────────────────────────────────────────────────
-
- If returned data type =
-
- string -> use as LPSTR
- long -> use as LPLONG
- date -> use as LPDOUBLE
- double-> use as LPDOUBLE
-
- typedef double FAR *LPDOUBLE /* defined in SQLAPI.H */
- typedef long int FAR *LPLONG /* defined in SQLAPI.H */
-
- 5
-
- BOOL SQLtoDlgListBox(string, bindstring, hDlg, DlgItem);
-
- LPSTR string; /* SQL SELECT statement */;
- LPSTR bindstring; /* null terminated bind data */
- HWND hDlg; /* handle of a dialog box */
- WORD DlgItem; /* item number of destination list box */
-
- Returns:
-
- TRUE if completed successfully.
- FALSE if error.
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- This function allows one function call (from a dialog box) to perform a
- complete SQLBase execute and fetch sequence. The fields are laid out as a
- single list box item with the fields separated by a blank space. Field
- width is maintained as returned by SQLBase as strings.
- ───────────────────────────────────────────────────────────────────────────
-
- 6
-
- int SQLToBiffFile(select, bindstring, bifffilename);
-
- LPSTR select; /* SQL SELECT statement */
- LPSTR bindstring; /* null-terminated bind data */
- LPSTR bifffilename; /* filename for BIFF file */
-
- Returns:
-
- Number of rows written.
- ─1 if error occurred.
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- This function allows one function call to perform a complete SQLBase
- execute-and-fetch sequence. The fields are laid out as cells within a new
- Excel Biff file.
- ───────────────────────────────────────────────────────────────────────────
-
- Bindstring and bifffilename may be NULL.
-
- 7
-
- int SQLErrorNum();
-
- Returns:
-
- An integer defining the type of error, if any, that occurred.
-
- ───────────────────────────────────────────────────────────────────────────
- Note:
- To maintain the design that hides SQLBase specifics, the SQLBase internal
- error number and information will be translated into DARWIN internal
- values yet to be defined in SQLAPI.H.
- ───────────────────────────────────────────────────────────────────────────
-
-
- Figure 18: DAR_COLUMNS contains information on the columns that comprise a
- table. For each column in a table there is one row in
- DAR_COLUMNS. Each row in the table is a column in DAR_COLUMNS.
-
- ╓┌──────────────┌────────────────┌─────────────┌─────────────────────────────╖
- ┌─────COLUMN NAME─────┐
- INTERNAL EXTERNAL DATA TYPE DESCRIPTION
- ┌─────COLUMN NAME─────┐
- INTERNAL EXTERNAL DATA TYPE DESCRIPTION
-
- Tablename Internal Char (15) The internal name of the
- Table Name table that contains the
- column.
-
- Columnname Internal Char (15) The internal name of the
- Column Name column.
-
- Printname Column Print Char (30) The print name or external
- Name name of the column as it
- appears in DARWIN's
- graphical representation
- of the data.
-
- Datatype Data Type Integer The data type of the column.
- 0 = Integer
- 1 = Real (floating point)
- 2 = Character
- 3 = Date
- ┌─────COLUMN NAME─────┐
- INTERNAL EXTERNAL DATA TYPE DESCRIPTION
- 3 = Date
-
- Precision Field Width Integer Contains the maximum
- number of digits or
- characters for datatypes
- INTEGER, REAL, or
- CHARACTER. If the
- column is a date datatype
- the field contains a 0.
-
- Fraction Fraction Size Integer The number of digits to the
- right of the decimal point.
- This is used for real
- (floating point) numbers.
-
- Resolution Data Integer This column is not
- Interpretation supported. The field = 0.
-
- Keyusage Key? Integer Indicates if the column is
- ┌─────COLUMN NAME─────┐
- INTERNAL EXTERNAL DATA TYPE DESCRIPTION
- Keyusage Key? Integer Indicates if the column is
- part of the primary key.
- 0 = Column is not part of
- primary key
- 1 = Column is part of
- primary key
-
- Domain Domain Integer This column is not used.
- The field = 0.
-
- Navalue N/A Value Char (30) This column is not used.
- The field = 0.
-
- Naused N/A Used Integer This column is not used.
- The field = 0.
-
- Dorder Display Order Integer The order in which the
- columns in the
- table are displayed.
- ┌─────COLUMN NAME─────┐
- INTERNAL EXTERNAL DATA TYPE DESCRIPTION
- table are displayed.
-
- Description Description Char (200) A text description of the
- of Column column.
-
- ████████████████████████████████████████████████████████████████████████████
-
- CodeView for Windows Provides an Interactive Debugging Environment
-
- Paul Yao and David Durant
-
- Microsoft(R) Windows and CodeView(R) are two products that have made a
- significant impact on the PC world, mostly because of their highly
- interactive user interfaces. The CodeView user interface is based on
- character-oriented windows and makes a debugging tool accessible in a way
- that was not previously available on microcomputers. The Windows user
- interface is a suggested style and a set of powerful tools used to
- implement this style.
-
- This article will examine CodeView for Windows (CVW), a product that is
- familiar to many DOS programmers, but, until now, has not been "Windows-
- aware." First, we'll look at why an interactive debugger is useful to
- programmers, followed by an examination of the major stumbling blocks of
- Windows programming and how to avoid some of them. After a brief
- discussion of CVW's hardware requirements, we will relate our
- experiences using a beta copy of CVW. Finally, a sample debugging
- session will trace the messages involved in the creation of an
- application's main window.
-
- CodeView employs many of the features found in Windows applications (see
- Figure 1), including drop-down menus, dialog boxes, windows, and mouse
- support. It is paradoxical that Windows programmers have for so long been
- unable to use a tool so akin to their own environment.
-
- There is a simple explanation for this: the two products were created
- independently by different development groups within Microsoft. CodeView
- was not originally developed with Windows applications in mind; it was
- geared toward the development of DOS applications.
-
- Although its interface is similar to that of Windows, CVW is not itself
- a Windows application in the usual sense. Instead, it is a special version
- of the original CodeView that has been made aware of the way in which the
- Windows memory manager juggles memory. What strikes you first when using
- CVW is that it does not share the screen with Windows; rather, it sends
- output to its own display monitor in exactly the same way that Symdeb,
- another interactive debugger, does.
-
-
- Interactive Debugger
-
- In the Windows programming classes that we teach, we are constantly
- astounded by the number of people who have never used an interactive
- debugger. There are certainly useful alternatives to an interactive
- debugger, such as the embedding of calls to MessageBox to display data
- and the inclusion of calls to fprintf to output trace information to a
- communications port. However, these techniques are cumbersome when
- compared with the power and flexibility available in an interactive
- debugger, where you have total control of your computer while your
- program is processing.
-
- Without an interactive debugger, you must stop, rewrite, and recompile
- your code before you can begin execution. An interactive debugger offers
- you the flexibility to decide how to investigate the operation of
- your program. It allows for spontaneous interaction with a running
- program at a level not otherwise possible.
-
- Getting started with an interactive debugger is no trivial task,
- however. Setting up a debugging environment takes time, and you must
- achieve a minimum proficiency with a debugger before it is truly useful.
- With the tight development schedules that programmers face, the
- preparation of a debugging environment somehow never gets done. Still, it
- is definitely worth the time; people who start working with an
- interactive debugger reap enormous benefits almost immediately.
-
-
- Development Problems
-
- Even with an interactive debugger, debugging a Windows application
- presents some unique challenges. It would be bad enough if the dynamic
- loading, movement, and discarding of memory objects were the only source
- of problems. However, the structure of Windows programs, the lack of
- hardware memory protection, and the need to be good at so many things,
- such as Windows Software Development Kit (SDK) routines, Intel(R)
- architecture, and C programming, contribute to the difficulties
- encountered when coding and debugging Windows applications.
-
- For some of these problems, the only solution is time and experience.
- Windows programming takes everything you know about programming and
- turns it on its ear──as the saying goes, everything you know is wrong.
- Taking advantage of the power of Windows/Presentation Manager
- programming requires you to learn a new way of thinking. Six months of
- full-time Windows programming, assuming prior experience with C, is a fair
- estimate.
-
- While you are gaining this experience, however, there is error checking
- available for free, provided by the compiler and Windows, itself. It
- requires only that you flip the right switches. Here are a number of
- suggestions for overcoming some common Windows development problems by
- using these free services.
-
- Always use the highest level of compiler warning switches (W2 or W3), and
- aim for a clean compile.
-
- Prototype every routine that you write. For examples of prototyping,
- just look at the WINDOWS.H file, where you will see declarations like the
- one shown below:
-
- BOOL FAR PASCAL
- TextOut (HDC, short, short, LPSTR, short);
-
- This declaration gives the compiler some very useful information, such
- as the return value (in the example above, Boolean), the calling
- convention (FAR and PASCAL), the number of parameters (5), and the type
- of each parameter.
-
- If you prototype your routines, you can quickly and easily eliminate some
- C programming errors. For example, in the course of development, it is
- common to add parameters to various routines. If, however, you fail to
- change every call to a routine that has one more (or one less) parameter,
- your application will contain a bug.
-
- This bug may bite you by causing your application to fail immediately. In
- this case, you will probably search until you find the problem. Or the bug
- may sit and wait, creating sporadic and unpredictable problems in your
- program. Because tthis type of bug can be found almost instantly,
- prototyping can save you much wasted time and effort and should always be
- used with a W2 or W3 compiler warning switch.
-
- Avoid casting. It is redundant in many situations, and can hide bugs in
- others. Let the compiler tell you about type mismatches, and in those
- cases respond by casting.
-
- The following casting (the two LPPAINTSTRUCTs and the LPSTR) is
- redundant.
-
- hDC = BeginPaint (hWnd, (LPPAINTSTRUCT)&ps);
- TextOut (hDC, 10, 10, (LPSTR)"It's a Long Way to Tipperary",28);
- EndPaint (hWnd, (LPPAINTSTRUCT)&ps);
-
- It was included in the earliest examples of Windows programs, however,
- because the C compiler did not support prototyping at that time. Every
- version of the Microsoft C compiler since C Version 4.0 supports prototyping
- and eliminates the need to cast in many circumstances. Prototyping enables
- the compiler to promote a near pointer to a far pointer, based on the
- prototypes in WINDOWS.H.
-
- Be sure that you include every window procedure, dialog box procedure,
- enumeration procedure, callback procedure, and subclass procedure found
- in the EXPORTS list of your application's module definition (DEF) file.
- This is a very common error and wastes much time. The lack of compiler and
- linker error checking in this regard makes this problem difficult to find.
-
- Install and regularly use a debugging version of Windows, not to be
- confused with a Windows-compatible debugger, such as CodeView. You create
- this by using special copies (from the SDK) of KERNEL.EXE, USER.EXE, and
- GDI.EXE.
-
- A debugging version of Windows performs error checking not available in
- the retail version of Windows. It catches problems like bad handles and
- wild pointers and notifies you via a debugging terminal, which you must
- have ready to receive the fatal exit messages, sometimes referred to as
- "RIP" (Rest In Peace) codes. Be sure to include the symbol files,
- KERNEL.SYM, USER.SYM, and GDI.SYM, which are used to display stack trace
- information when RIP code is generated.
-
- With a debugging version of Windows installed, be sure to set the
- following switches in your WIN.INI file:
-
- [kernel]
- EnableHeapChecking=1
- EnableFreeChecking=1
-
- This causes all free bytes to be filled with the value CH. This protects
- you in two ways: first, if a wild pointer from your program causes a
- write to occur in a free block, a fatal exit message is sent to your
- debugging terminal. Second, you are protected if your program tries to
- jump to a free block. The value CCH translates into the machine
- instruction INT 3, which is a call to the debugger. A wild jump, then,
- causes the debugger to come alive, allowing you to display a stack
- trace and determine the part of your program from which execution
- departed. Because of these benefits, the use of these switches is strongly
- recommended.
-
- If you are using Expanded Memory Specification (EMS) memory (or running
- Windows/386, which has built-in EMS support), make sure you set the
- following switch in WIN.INI:
-
- [kernel]
- EnableEMSDebug=1
-
- This switch makes sure the debugger is told when EMS pages are banked in
- and banked out and lets you set breakpoints within the discardable code
- segments of dynamic-link libraries, where many Windows SDK routines
- reside.
-
-
- Hardware Setup
-
- CVW has some unique hardware requirements, such as the need for a second
- monitor. We will review the choices available and discuss the need for
- EMS 4.0 memory. If you have been using Symdeb, the setup of the second
- monitor is the same, and you are ready for CVW.
-
- You have two choices: a monochrome display adapter (MDA) with a monochrome
- monitor or an asynchronous terminal with a null-modem adapter. The first
- choice is the preferable one, because you can then use CVW in full-screen
- mode. An asynchronous terminal is useful, though, since you can take
- advantage of the access to local variables that CVW provides. However, you
- must operate in line mode without full-screen support.
-
- When operating in full-screen mode, CVW lets you use the mouse to select
- menu items, and control its operation (when CVW has control). In this
- situation, CVW cooperates nicely by sharing the mouse with Windows.
- However, you must load a DOS mouse driver, which CVW will then use for its
- mouse support. Even if you don't plan on using a mouse (in line mode, for
- example), you still must install a driver for it. Otherwise, the Windows
- mouse freezes up, although you can still operate Windows with the keyboard
- interface commands. We hope this problem is corrected before CVW is
- released.
-
-
- EMS 4.0 Memory
-
- CVW requires you to have EMS memory present. CVW takes up 138Kb of main
- system memory and then employs EMS memory for its code, data, and symbol
- space. The available options depend on the specific Windows product that
- you are using. With Windows 2.x, you must have an EMS board and an EMS 4.0
- or later driver. We experimented with an Intel Above(TM) Board, as well as
- with an AST(R) RAMpage!(R) board. Both worked well, but you must make sure
- that you have the correct version of the driver.
-
- EMS 4.0 support is built into Windows/386, which CVW can use. There are
- two things to keep in mind here. First, you must have a minimum of 640Kb
- main memory and 512Kb extended memory in order for EMS support to be
- available under Windows/386. Second, you must play a trick on Windows/386
- so that its EMS support is loaded first; you then start CVW before
- starting Windows. This process is described in more detail in the
- documentation.
-
-
- CodeView and Windows
-
- Now that CodeView is available for debugging Windows applications,
- Windows programmers may ask if its debugging capabilities can really be
- adapted to the unique and extremely complex Windows environment. The
- answer is yes, but it is a qualified yes. Let's look at CodeView's
- strengths and weaknesses in the Windows environment──remember that our
- experience is based on a beta release of the software.
-
-
- Windows Programs
-
- As every Windows programmer quickly learns, a Windows application
- program does not control its own destiny; it lives at the mercy of
- messages sent by the user, by Windows, by other programs, and by itself.
- Windows programs do not always have control of the processor; instead
- they spend most of their time suspended, waiting for a message to arrive.
-
- When a message is delivered to a Windows program, it gains control of the
- processor and must respond to the particular message. The message is often
- delivered to the GetMessage processing loop. From there control is
- transferred to the appropriate window procedure. The decision is based
- on a piece of information that is part of a message, the window handle.
-
- Once a window procedure receives a message, it determines the message
- type and typically branches to a subprocedure referred to as a message
- procedure. Each message procedure is independent of the others in the
- sense that the processing of one message cannot assume that some other
- message has already been received or will subsequently be received. It can
- only check the current state of the application, such as the currently
- active cell of a spreadsheet, and react accordingly.
-
- The classic example of the extent to which Windows applications must
- react to messages is the WM_PAINT message. In response to a WM_PAINT
- message, a window procedure must be capable of recreating the contents
- of the client area at any time, for any reason, and under any
- circumstances. A Windows program, then, tends to be reactive; it is the
- user and not the program that controls the flow of processing.
-
- A Windows program can be thought of as a state machine, with the state
- stored in external variables. Windows programs also differ from normal
- programs in that they are object-oriented. Window classes, windows,
- display contexts, GDI objects, and loaded resources are all objects in a
- general sense: they are identified by a handle, you do not have to be
- concerned with the underlying data structures, and a given type of
- object, such as a window, can be manipulated in a standard set of ways
- (any window can be moved, sized, repainted, sent a message, and so on).
-
- Also, Windows is a library-oriented environment, specifically dynamic-
- link libraries. All the toolkit routines are contained in dynamic-link
- libraries. You can use dynamic-link libraries to share code, resources,
- data, and to control the allocation of some limited resource, such as
- system memory, or a local area network link.
-
-
- CodeView Debugging
-
- Programs have two parts: code and data. One of the chief uses of an
- interactive debugger like CodeView is to enable you to follow the path
- that your program takes through the code. During the course of such a
- "trace" operation, you can view and even modify the data on which your
- code operates. CodeView offers three methods of tracing execution:
- executing the program one line at a time, executing the program in "slow
- motion," and executing the program at normal speed but halting its
- execution at predetermined points within the code or based on prespecified
- data conditions.
-
- Executing the program one line at a time means either one source line or
- one object instruction at a time, depending on the mode that CodeView is
- currently in. Running in slow motion means that approximately four
- instructions per second will be executed and that the display of code and
- data are synchronized with program execution, continuously updated to
- reflect the current program location and data values.
-
- At any time you are running in slow motion, execution of the program can
- be halted to permit other interactions with CodeView. Hereafter, running
- the program in slow motion will be referred to as executing the program,
- while running the program at normal speed, which requires the Go command,
- will be referred to as just running the program.
-
- The third technique, running the program at normal speed and halting its
- execution at predetermined points, consists of setting breakpoints,
- watchpoints, and tracepoints. Setting a breakpoint means specifying a
- point in the code at which program execution is to halt. This is done by
- finding that line of code in the code display window and clicking upon it
- with a mouse or by using the breakpoint set command, bp. When you do
- this, the line will be displayed in high intensity. You can remove the
- breakpoint by clicking on the line a second time, which will return it to
- normal intensity. As many as 19 breakpoints can be set at a time.
-
- A brief word about an idiosyncrasy of CVW: CVW indicates that a
- breakpoint has been set by highlighting a line of code. However,
- highlighting also indicates the current line when tracing execution. These
- two unrelated uses make it difficult to determine if the current line is
- set as a breakpoint or not. To find out, you simply have to trace
- execution to another line.
-
- Just as breakpoints are execution halts set within the code, watchpoints
- and tracepoints are execution halts based on data values. The watchpoint
- is a conditional statement involving one or more program variables, such
- as "character count==80". Program execution halts at the instruction and
- causes the value of watchpoint to become true.
-
- A tracepoint is also a statement involving one or more program variables,
- such as iIndex, but it need not be a conditional statement. This is
- because the program is going to halt anytime the value of a tracepoint
- changes, regardless of what value it changes to.
-
- One nice thing about breakpoints, watchpoints, and tracepoints is that
- they remain in effect even after the program is finished executing. Thus
- you can set the watchpoints and breakpoints and run the program several
- times without having to reset them.
-
- One not-so-nice thing about watchpoints and tracepoints concerns local
- variables. Local variables, as most programmers know, are in effect only
- while the routine in which they are defined is actually being executed
- and are thus known only to CodeView at that time. Therefore, if you wish
- to set a watchpoint or a tracepoint involving a local variable, you must
- first set the breakpoint and execute the program up to that breakpoint so
- that the halted execution is now within the routine that contains the
- local variable. Once you have done this you can then define the
- watchpoints and tracepoints that involve that local variable, remove the
- breakpoint, and continue to execute or run the program.
-
- A limitation of CodeView or any interactive debugger is that input is
- shared between your program and your debugger. Your program needs input,
- of course, which it receives in the form of mouse and keyboard input, and
- debuggers also require input. In the case of CodeView, both keyboard and
- mouse input can be used. However, if you wish to debug mouse or keyboard
- input, switching into a debugger can be annoying. There are ways around
- this, of course, and we mention them not to detract from CodeView, but
- merely to identify an area that requires special attention when using an
- interactive debugger.
-
- For example, setting a breakpoint to trace through WM_MOUSEMOVE
- processing can be difficult, if not impossible. Another limitation
- inherent in any interactive debugger is that, with the interruptions
- caused by breakpoints and the ability to trace program execution, time-
- dependent operations can be difficult to debug. Thus, CVW is limited in
- its ability to work with programs dependent on WM_TIMER messages for their
- normal operation. Also, if there are external, real-time events that your
- Windows program must track, the time-dependent portions of your program
- may be impossible to debug with an interactive debugger.
-
-
- Windows Debugging
-
- Given that Windows programs react to messages, can be viewed as state
- machines, and are in an object-oriented and dynamic-link library
- environment, how can CVW help in debugging Windows programs? CVW handles
- the reactive nature of a Windows application program very well because of
- its ability to trace the flow of execution through code. By setting
- breakpoints, watchpoints, and tracepoints, you can see which message types
- are being received and what a program does in response to any given
- message. Later on, we will do just that by tracing the message traffic
- associated with the creation of an application's main window.
-
- CVW also fits well with the state-machine nature of a Windows application
- program. Since the state of the application is maintained in program
- variables and the user can watch those program variables change as the
- program runs, even halting execution when the variables reach some
- unexpected value, the user can remain aware of the state of the program as
- it is running and detect problems in that area.
-
- CVW is of limited use in dealing with the object-oriented nature of a
- Windows program. This is not so much a flaw in CodeView as simply a result
- of the fact that objects are kept outside the program's data segment. Pens,
- brushes, fonts, bitmaps, globally allocated memory objects, and the data
- structures that describe a window are beyond easy access for CodeView.
-
- If you wish to look at the contents of a memory object, a cursor, icon, or
- bitmap, HeapWalker is your best bet. If you want to find out about a GDI
- object, you can call the GetObject routine to transfer values into a
- program variable that can then be viewed with CodeView. If you wish to see
- information on any other object, such as a window, use the appropriate
- toolkit routine, GetWindowWord for example, to transfer the
- information to a program variable.
-
- CVW is helpful in an object-oriented environment because you can look at
- handles and pointers, which are, after all, located within your program's
- variables. By setting tracepoints you can detect changes in the value of
- handles, and you will be able to trap events like the unintentional
- overwriting of a handle or pointer value. Also, you can determine when a
- null handle has been returned and be certain that a failure has
- occurred.
-
- CVW is also useful in working with dynamic-link libraries. CVW permits
- you to perform source-level tracing into your dynamic-link libraries,
- giving you a more complete picture of what is happening during the execution
- of your application.
-
- If you want to view the code contained in one of the Windows dynamic-link
- libraries, such as Kernel, User, or GDI, CVW lets you do this. However,
- its usefulness depends on whether you understand 8086 assembly language
- or not. Because you do not have the source code to these dynamic-link
- libraries, you can only trace through them on a machine-language level.
- If you are reasonably proficient in reading 8086 assembler, this can be
- invaluable for determining exactly what an SDK routine is doing under a
- particular set of circumstances.
-
-
- Sample Session
-
- We are now ready to walk through a sample CVW debugging session. Our
- purpose is to show you how setting a breakpoint on a window procedure's
- switch statement allows you to watch the messages associated with the
- creation of a window.
-
-
- Getting Started
-
- The program that we will trace is the "Hello Windows" program from the
- Sample Source Code disk of the Windows Version 2.03 SDK, which you should be
- able to copy from the Windows SDK to walk through this debugging session
- yourself.
-
- The first step is to modify the program's make file so that it has the
- appropriate compiler and linker switches. This involves adding the Zi and
- Od switches to the compile line and the /CO switch to the link line. The
- complete make file is shown in Figure 2.
-
- Once you have recreated HELLO.EXE, you start CVW by using one of the two
- command lines that are shown in Figure 3. If you are using an MDA adapter
- with a monochrome display, which enables full-screen mode debugging, use
- the first command line shown in Figure 3. If you are using an
- asynchronous terminal with a null-modem adapter as your debugging
- workstation, which enables line-mode debugging, use the second command line.
-
- In the rest of this sample debugging session, full-screen debugging mode
- is used. When CVW starts, you will see the two screens shown in Figures 4
- and 5.In Figure 4, you see that CodeView has started. In Figure 5
- however, you see that Windows itself has not yet started.
-
-
- Setting Breakpoints
-
- First, set a breakpoint at the window procedure's switch statement, which
- involves locating a source line and then either clicking with the left
- button or using the bp command to indicate the necessary source line.
- Figure 6 shows a breakpoint set with the bp command highlighted.
-
-
- Go
-
- Next, start execution. When you issue the Go command, you first notice
- that Windows starts running. Figure 7 shows the display screens after
- Windows has started, when the system has encountered the breakpoint set at
- the switch statement in the Hello program's window procedure, HelloWndProc.
- Note that the Windows display contains the familiar blue of the desktop,
- although nothing currently occupies it. In the CodeView display, you'll see
- that you have mouse control and are able to issue CodeView commands.
-
-
- Adding to the Watch List
-
- At this point, we would like to add the local variable "message" to the
- watch list. Because we want the value to be displayed in hexadecimal, a type
- modifier is added to the w? command. We add message to the watch list by
- typing the following at the CodeView prompt:
-
- > w? message, x
-
- Figure 8 shows that the "watch window" has been opened and that the
- current value of message (0024H) is being displayed. A quick look at
- WINDOWS.H reveals that this value represents the WM_GETMINMAXINFO message.
- Even though a message has been received for our window, it has not been
- processed yet. To allow the message to process, we again issue the g
- (GO) command, after which another breakpoint is encountered, and the
- display appears as shown in Figure 9. Notice that the CVW display shows
- that the value of message is 0081H and that nothing seems to have changed
- in the Windows display.
-
- The messages next received are:
-
- WM_GETMINMAXINFO
- WM_NCCREATE
- WM_CALCSIZE
- WM_CREATE
- WM_SHOWWINDOW
- WM_SETVISIBLE
- WM_ACTIVATEAPP
-
-
- The Nonclient Area
-
- The first message that causes anything to appear on the Windows display
- is the message WM_NCACTIVATE. The NC part of the message stands for
- nonclient. As Figure 10 shows, this simply draws the border of the window,
- along with the System Box and the Minimize and Maximize Boxes.
-
- The very next message, WM_GETTEXT, seems to cause the caption to be drawn.
- In fact, we suspect that the prior WM_NCACTIVATE sent a WM_GETTEXT message
- to retrieve this Windows message so as to properly display the caption
- information (see Figure 11).
-
- Another group of messages appear that affect the display. These messages
- are:
-
- WM_ACTIVATE
- WM_SETFOCUS
- WM_NCPAINT
- WM_SYNCPAINT
-
-
- Erasing the Background
-
- Figure 12 demonstrates the effect of the message WM_ERASEBKGND. Clearly,
- the background of the window has been erased with the main window's default
- background brush, which is a white brush. A pair of messages then appears:
-
- WM_SIZE
- WM_MOVE
-
- You should note that the size and the move messages appear at window
- creation time, which is useful to know if you plan to respond to these two
- messages. Think of these messages as saying, "Your window is changing in
- size from nothing to something and moving from nowhere to somewhere."
-
-
- Hello Windows!
-
- Finally, when a WM_PAINT message appears, the familiar "Hello Windows"
- message, shown in Figure 13, is displayed. It seems that so many messages
- have flown by as part of the window's creation process, but there are still
- two messages coming in, namely:
-
- WM_NCHITTES
- WM_SETCURSOR
-
- The last message to appear is the WM_MOUSEMOVE message. This concludes
- the immediate window creation message traffic. In all, 20 messages were
- received by this window procedure before the window creation process was
- complete.
-
- At this point, if you move the mouse cursor, another WM_MOUSEMOVE message
- is sent to the application's window procedure. You need to disable the
- breakpoint; otherwise you would be trapped forever by mouse movement
- messages. You do this either by clicking again on the breakpoint (to
- toggle the breakpoint) or by issuing the following command:
-
- > bc 0
-
-
- Quitting CodeView
-
- Before you quit CodeView, you need to close down Windows. This is because
- Windows modifies the system, including several system vectors that must be
- restored if the system is to operate in a normal fashion. Once this is
- done, you exit CodeView with the Quit command:
-
- > quit
-
-
- A Breakthrough
-
- When it becomes available, CodeView for Windows will be a breakthrough for
- Windows developers. Its interface is intuitive and easy to use. Its ability
- to access local variables, and to display data structures and evaluate
- complex C expressions gives it power that puts it in a class by itself.
-
- However, its main memory requirement (138Kb) and the necessity for EMS
- memory may limit its usefulness for some developers. In contrast, Symdeb
- (43Kb), although lacking the full-screen user interface and access to
- local variables that are part of CodeView, will always be useful for certain
- Windows developers. Also, the copy of CodeView that we looked at did not
- have some of the internal Windows hooks that Symdeb has, such as the ability
- to dump the global heap, dump the free list, and so on. This is a minor
- issue, however, when compared with the many great advantages that CodeView's
- user interface provides.
-
-
- Figure 2: The complete make file for compiling the Hello program, to be
- used with CodeView.
-
- #
- # Standard command line definitions
- #
- cp=cl -d -c -AS -Gsw -Od -Zpie
- #
- # Standard inference rules
- #
- #.c.obj:$(cp) $
- .c
- #
- # The C File List
- #
-
- hello.obj: hello.c hello.h
-
- hello.res: hello.rc hello.ico hello.h
- rc -r hello.rc
-
- hello.exe: hello.obj hello.res hello.def
- link4 hello/CO,/align:16,,slibw/NOE,hello.def
- rc hello.res
-
-
- Figure 3: CodeView for Windows can be started in several ways, depending
- depending on what type of secondary monitor or terminal is in use.
-
- Command when using an MDA adapter:
-
- C> cvw /2 /L hello.exe \windows\win.com hello.exe
-
- Command when using an asynchronous terminal and null modem adapter:
-
- C> cvw /C=com1 /T /L hello.exe \windows\win.com hello.exe
-
- ████████████████████████████████████████████████████████████████████████████
-
- OS/2 Graphics Programming Interface: An Introduction to Coordinate Spaces
-
- Charles Petzold
-
- PIXELS.
- You can't draw with them.
- You can't draw without them.
-
- Pixels are the single greatest impediment to device-independent graphics
- programming. Part of the problem is that different output devices have
- different pixel resolutions. A 150-pixel-high image is 5 inches tall on a
- CGA display but only a half inch on a 300-dots-per-inch laser printer.
- Moreover, many display output devices have different horizontal and
- vertical resolutions. If you are working with pixels, you will have
- trouble drawing round circles and square squares.
-
- To avoid these problems, many high-level graphics programming languages
- give the programmer a means to draw in units other than pixels. The
- programmer specifies coordinate positions in terms of inches,
- millimeters, or other convenient units. The graphics system then converts
- these coordinates to pixels when drawing.
-
- The conversion of points from one coordinate system to another is called a
- "transform." At the very least, transforms should hide from the program
- the resolution of the output device and any differences in horizontal and
- vertical resolution. But transforms also play an important role in
- "modeling"─the construction of images from individual elements.
- Transforms provide the means to manipulate these elements before they are
- displayed.
-
- The GPI (Graphics Programming Interface) component of the OS/2
- Presentation Manager supports transforms both for working in units other
- than pixels and for more sophisticated modeling tasks. In fact, the GPI
- contains so many transform functions that the subject can be quite
- confusing to the GPI newcomer.
-
- This article discusses the GPI transforms that are supported under the
- "micro-presentation space" (or micro-PS). As I discussed in "The Graphics
- Programming Interface: A Guide to OS/2 Presentation Spaces," (MSJ, Vol.
- 3 No. 3), the micro-PS supports only a subset of GPI functions. However,
- working with a subset can certainly be an advantage when learning a new
- topic because you can simply ignore everything not included in the
- subset.
-
-
- I Point, Therefore I Am
-
- When you draw with GPI functions, you specify a two-dimensional point
- using the POINTL structure. The POINTL structure contains two fields named
- x and y. GPI maps the point you specify in the POINTL structure to a point
- on the output device (which can be the screen, a printer, or a plotter).
-
- At first, this seems like simple analytic geometry. The points you use in
- GPI functions are simply points in a Cartesian coordinate system, as shown
- in Figure 1. We can represent a point in this coordinate space by the
- notation (x,y). The point (0,0) is the center where the horizontal and
- vertical axes meet.
-
- This is good for starters, but its simplicity is deceptive. Let's pose a
- few basic questions:
-
- ■ Where is the origin? That is, when you use a POINTL structure with
- the x and y fields both set to zero, where is that point mapped to on
- the output device?
-
- ■ How do units of x and y in the POINTL structure correspond to the
- natural pixel units of the output device? Do increasing values of x
- really go to the right? Do increasing values of y really go up?
-
- ■ Is it necessary for values of x to represent a position on the
- horizontal axis and for values of y to represent a position on the
- vertical axis? Can the entire coordinate system be rotated in some
- way to look like Figure 2?
-
- ■ Is it even necessary for the x and y axes to be at right angles to
- each other? Or can the coordinate system look like the one that is
- shown in Figure 3?
-
- The answers to these questions are: whatever you want them to be. Using
- GPI transforms, you control exactly how the points you specify in GPI
- functions are mapped to the output device.
-
- In fact, these four sets of questions are related to the four basic types
- of transforms:
-
- ■ Translation: Where the point (0,0) is mapped to on the output
- device.
-
- ■ Scaling: How units of x and y correspond to pixels.
-
- ■ Rotation: How the x and y axes are oriented.
-
- ■ Shear: Whether the two axes are at right angles to each other or
- not.
-
- We'll examine these four types of transforms in more detail later in this
- article.
-
-
- Visualizing Transforms
-
- One way of thinking about graphics transforms is to imagine the axes
- shown in Figures 1, 2, and 3 as being superimposed on an output device,
- such as the video display. The points you use in GPI functions specify a
- point in the coordinate space. That point corresponds to a particular
- pixel position on the screen.
-
- Using GPI transform functions, you can effectively shift the axes so that
- the origin is at a different location on the screen. You can stretch or
- compress an axis so that the units you use relate to pixels in different
- ways. You can rotate the axes so that objects you draw appear to be
- rotated. You can tilt one or both axes so that objects are distorted.
-
- But you'll find that this visual approach is not the best way to think
- about GPI transforms. It's relatively easy if you're dealing with only two
- coordinate systems (the coordinate space in which you draw and the
- natural coordinate space of the output device), but the micro-PS subset of
- GPI defines three additional coordinate systems between these two. You'll
- find that the visual approach is inadequate and that instead you must rely
- on formulas. These formulas govern how a point is mapped from one
- coordinate space to the next.
-
-
- Five Coordinate Spaces
-
- The five micro-PS coordinate spaces are shown in the table in Figure 4.
- The "Transform" column of the table shows the name of the transform that
- maps between each pair of coordinate spaces. The last column shows the GPI
- function that lets you set or change the transformation formula.
-
- The points you specify in GPI drawing functions are in world coordinate
- space. These points are eventually mapped to the output device, which is
- the media coordinate space.
-
-
- The Media and the Device
-
- The best place to start is at the bottom of Figure 4 with the windowing
- system transform. This is the easiest transform because it's one you can't
- control and therefore do not have to worry about. The windowing system
- transform is handled within the Presentation Manager.
-
- Media coordinate space is dependent on the hardware of the output device.
- The video display, for example, is organized in rows and columns of
- pixels. Each pixel is one unit in media space. For convenience, the
- origin-the point (0,0)-is defined to be the lower-left corner of the
- display. Increasing values of x go to the right; increasing values of y
- go up.
-
- But you never use media space coordinates in GPI functions. To draw on
- the display, you need a handle to a presentation space. The presentation
- space is always identified with a particular window. The window has its own
- coordinate system known as device space. The origin is at the lower-left
- corner of the window. Like media space, units are pixels. Increasing values
- of x go to the right; increasing values of y go up.
-
- The windowing system transform maps from device space to media space.
- This transform is based on the position of the window relative to the screen.
- pixels from the left side of the screen and 100 pixels from the bottom.
- The windowing system transformation formulas are:
-
- xmedia = xdevice + 50
- ymedia = ydevice + 100
-
- Thus, the point (0,0) in device space is mapped to the point (50,100) in
- media space. These are simple transformation formulas that effectively
- shift the origin of the coordinate space. This type of transform is called
- a "translation."
-
-
- The Presentation Page
-
- The presentation page (also known more simply as the page) is the
- coordinate space in which a picture is constructed for display. You can
- visualize the page as precisely that──a page of paper. On the screen, you
- view the whole page or a part of the page in a program's window.
-
- The units of the page can be pixels or other units of measure (such as
- inches or millimeters) or anything else that is convenient for the
- application. The lower-left corner of the page usually coincides with the
- lower-left corner of device space (the window), but it doesn't have to.
-
- The presentation page maps to device space through the device transform.
- This is the transform that allows a program to work in units other than
- pixels.
-
- When you create a presentation space by calling the function
- GpiCreatePS, the device transform is set for you. In the GpiCreatePS
- function, you specify the units of the presentation page (for example, by
- using the identifier PU_LOMETRIC for units of 0.1 millimeter or the
- identifier PU_LOENGLISH for units of 0.01 inch) and the size of the page
- in those units.
-
- The size of the page in page units is specified in a SIZEL structure that
- is then passed to GpiCreatePS. The SIZEL structure has two fields named
- cx and cy. For example, if you decide to use PU_LOENGLISH page units and
- you want the page to be 81/2 by 11 inches, set cx to 850 and cy to 1100.
- If you use 0 values in the SIZEL structure, the size of the page is set
- equal to the size of the output medium. For a window on the display, this
- is the size of the screen.
-
- The device transform is defined in terms of a RECTL (rectangle) structure.
- After you call GpiCreatePS to create a presentation space, you can obtain
- the device transform rectangle by calling
-
- GpiQueryPageViewport ( hps, &rcl) ;
-
-
- where rcl is a structure of type RECTL. You will find that the values are
- set as is shown in Figure 5.
-
- The device transform formulas, shown in Figure 6, are rather ugly. The
- rcl variable (a structure of type RECTL) is the device transform
- rectangle, and sizl (a structure of type SIZEL) contains the page size in
- page units.
-
- If the xLeft and yBottom fields of the RECTL structure are 0 (as they are
- when you first create the presentation space), then these two formulas
- reduce to those shown in Figure 7.
-
- This transform does exactly what we expect: it transforms the page units
- (which could be 0.1 millimeter or 0.01 inch) into pixels by multiplying them
- by a factor that relates those units to the pixel size. This type of
- transform is known as "scaling." The complete formula also allows for
- translation.
-
- The size of the page that you specify in the GpiCreatePS function does
- not affect the device transform. Whatever page size you choose, GPI will
- calculate a device transform appropriate to it. However, the page size
- affects clipping because the graphics field is initially set to the size of
- the page.
-
- If you create a presentation space using GpiCreatePS, you will probably
- want to leave the device transform alone. However, you can do some tricks
- with it, such as setting different scaling factors or translating the
- origin of the presentation page to someplace other than the lower-left
- corner of the window.
-
- For example, if you wish to put the origin in the middle of the window,
- first obtain the device transform rectangle by calling
- GpiQueryPageViewport, add half the width of the client window to the
- xLeft and xRight fields and half the height of the client window to the
- yBottom and yTop fields, and then set the new device transform by calling
- GpiSetPageViewport. You'll also need to change the graphics field
- clipping area to encompass the whole window in page units.
-
- Windows programmers should note that the function GpiSetPageViewport
- does the job of several Windows functions: SetViewportOrg,
- SetWindowOrg, and SetViewportExt, in addition to SetWindowExt.
- However, it is not quite as easy to use as the Windows functions.
-
-
- Cached Micro-PS
-
- If you use a cached micro-PS, the presentation page is always in units of
- pixels. You can use the device transform to change that. For example,
- suppose you want to draw in units of 0.01 inch. You first obtain a handle
- to the window device context:
-
- hdc = WinOpenWindowDC(hwnd) ;
-
- You then make four calls to DevQueryCaps to determine the dimensions of
- the screen in pixels and the number of pixels per meter:
-
- DevQueryCaps (hdc, CAPS_WIDTH, 1L, &xScreenPixels) ;
- DevQueryCaps (hdc, CAPS_HEIGHT, 1L, &yScreenPixels) ;
- DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION, 1L, &yPixelsPerMeter) ;
- DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &xPixelsPerMeter) ;
-
- Because the size of the cached micro-PS page is the size of the screen,
- you define the fields of a RECTL structure like this:
-
- rcl.xLeft = 0 ;
- rcl.yBottom = 0 ;
- rcl.xRight =
- ((xScreenPixels - 1) * xPixelsPerMeter * 254 + 500000) / 1000000 ;
- rcl.yTop = ((yScreenPixels - 1) * yPixelsPerMeter * 254
- + 500000) / 1000000 ;
-
- The ratio 254/1,000,000 converts meters to 0.01 inch. The number 500,000
- is for rounding. Thus, the xRight and yTop fields are set to the width and
- height of the screen in 0.01 inch. You then call:
-
- GpiSetPageViewport (hps, &rcl) ;
-
- You also need to set the graphics field to the size of the window in
- page units. To obtain this, you can use the GpiConvert function to convert
- from device space to page coordinates:
-
- WinQueryWindowRect (hps, &rcl) ;
- GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 2L, (PPOINTL) &rcl) ;
- GpiSetGraphicsField (hps, &rcl) ;
-
- And you're done.
-
- Obviously the device transform is not easy to work with. It's easiest to
- create a micro-PS using GpiCreatePS and specify the page size and page
- units you want. That defines the device transform once and for all.
-
-
- Constructing the Picture
-
- If you don't use a transform, other than the device transform as it is
- set by GpiCreatePS, then everything you draw using GPI functions is in
- page units-the units you selected in the GpiCreatePS function. (Actually,
- there are some exceptions: GPI regions are always defined in device
- units, for example.) The origin is located in the lower-left corner of
- the window. Increasing values of x move to the right; increasing values of
- y move up.
-
- As you construct a picture on the presentation page, you also have
- available two additional transforms: the modeling transform and the
- default viewing transform. The points you use in GPI functions are in
- world coordinate space. The modeling transform converts those points to
- model coordinate space. The default viewing transform converts the points
- in model space to points in the presentation page space.
-
- For some simple purposes, the modeling and default viewing transforms are
- interchangeable. But a general rule to follow is this: the transforms
- closest to the hardware (that is, the transforms at the bottom of
- Figure 4) should be changed less frequently than the transforms furthest
- from the hardware. The simplest approach is to use the default viewing
- transform to apply to the whole picture and different modeling transforms
- to apply to pieces of the picture.
-
- Suppose you want to work with a coordinate system in which the center of
- the window is the origin. You use the default viewing transform to
- accomplish that. (The TRANSFRM program discussed later does exactly
- that.) Suppose you want to draw a picture in which a particular object
- appears in several different places. You can draw the object with
- different modeling transforms. The same points are passed to the GPI
- drawing functions, but the modeling transform causes those points to be
- transformed.
-
-
- Transformation Matrix
-
- Both the modeling transform and the default viewing transform are based
- on a standard two-dimensional "transformation matrix" common in many
- high-level graphics languages such as GKS (Graphical Kernel System).
-
- In GPI, you specify a transformation matrix as a structure of type
- MATRIXLF. (The LF part of the structure name stands for LONG and FIXED,
- the two data types used for the structure fields.) The MATRIXLF structure
- is defined in the PMGPI.H header file, like this:
-
- typedef struct _MATRIXLF
- {
- FIXED fxM11 ;
- FIXED fxM12 ;
- LONG lM13 ;
- FIXED fxM21 ;
- FIXED fxM22 ;
- LONG lM23 ;
- LONG lM31 ;
- LONG lM32 ;
- LONG lM33 ;
- }
- MATRIXLF ;
-
- At first glance, the jumble of numbers in the field names looks like a
- real mess. But it's actually quite simple. The nine fields of this
- structure are the nine elements of a 3-by-3 matrix:
-
- │ fxM11 fxM12 lM13 │
- │ │
- │ fxM21 fxM22 lM23 │
- │ │
- │ lM31 lM32 lM33 │
-
- The M in the field names stands for "matrix," and the numbers are simply
- the row and column positions of each field within the matrix.
-
- The letter l prefix on some of the fields stands for LONG. The fx prefix
- stands for FIXED. The FIXED data type is a 32-bit signed long integer that
- is interpreted by GPI as a 16-bit signed integer and a 16-bit unsigned
- fraction. The FIXED data type allows GPI to use fractions without
- resorting to time-consuming floating-point operations. Figure 8 shows
- various examples of FIXED values.
-
- In some of the following examples I use normal decimal point notation for
- FIXED numbers. Keep in mind that these are really 32-bit integers.
-
- You can convert a FIXED variable to a float or double variable by dividing
- by 65536.0:
-
- dNumber = (fxNumber / 65536.0) ;
-
- You can convert a float or double variable to FIXED by multiplying by
- 65536 and casting the result:
-
- fxNumber = (FIXED) (65536 * dNumber) ;
-
- The transformation matrix is actually slightly simpler than the version
- shown above. When you use a MATRIXLF structure in the GPI transform
- functions, the lM13 and lM23 fields must be set equal to 0 and the lM33
- field must be set equal to 1:
-
-
- │ fxM11 fxM12 0 │
- │ │
- │ fxM21 fxM22 0 │
- │ │
- │ lM31 lM32 1 │
-
- GPI uses a matrix multiplication to convert a point (x,y) to a point
- (x',y'). First, GPI constructs a 1-by-3 matrix from the original point
- (x,y) by appending a 1. Then that matrix is multiplied by the 3-by-3
- transformation matrix. The resultant 1-by-3 matrix contains the points
- x', y', and 1 and is shown in Figure 9.
-
- If (x,y) is a point in world coordinate space, then GPI applies the
- modeling transform to determine the point (x',y') in model coordinate
- space. If (x,y) is a point in model coordinate space, then GPI applies the
- default viewing transform, and the resultant point (x',y') is in
- presentation page units.
-
- The transformation matrix can also be represented as a pair of formulas
- that describe the matrix multiplication:
-
- x' = (fxM11) x + (fxM21) y + lM31
- y' = (fxM12) x + (fxM22) y + lM32
-
- Note that the points x' and y' are functions of both x and y. This is not
- the case in the device transform and windowing system transform.
-
- For a new presentation space, the modeling and default viewing
- transformation matrices are both identity matrices. Only the fxM11, fxM22,
- and lM33 fields are set to 1. The other fields are set to 0:
-
- │ 1.0 0.0 0 │
- │ │
- │ 0.0 1.0 0 │
- │ │
- │ 0 0 1 │
-
- Thus, the transformation formulas look like this:
-
- x' = x
- y' = y
-
-
- The TRANSFRM Program
-
- The TRANSFRM program shown in Figure 10 allows you to experiment with the
- GPI transformation matrix.
-
- The micro-PS is created in ClientWndProc during the processing of the
- WM_CREATE message. It uses page units of PU_LOENGLISH, or 0.01 inch.
- During the WM_PAINT message, the window procedure draws a ruler and a
- stick figure enclosed in a box, as shown in Figure 11. The ruler has tick
- marks every inch (100 units). The figure is enclosed in a box that is 2
- inches high and 2 inches wide.
-
- TRANSFRM sets the default viewing transform while processing the WM_SIZE
- message. This sets the origin of model space to the center of the window.
- The ruler is always drawn with a default modeling transform, which is set
- during the WM_PAINT message with the following function call:
-
- GpiSetModelTransformMatrix ( hps, 0L, NULL, TRANSFORM_REPLACE) ;
-
- The figure is drawn with a modeling transform that you define in a dialog
- box (see Figure 12). This transform is set using the MATRIXLF structure
- named matlfModel:
-
- GpiSetModelTransformMatrix ( hps, 9L, &matlfModel, TRANSFORM_REPLACE) ;
-
- In all cases the figure is drawn from the DrawFigure function using the
- same array of POINTL structures. Different modeling transformation
- matrices, however, can change the appearance of the object.
-
-
- Translation
-
- Suppose you have in your program an array of POINTL structures that
- defines a 100-unit square with the bottom-left corner at point (0,0). You
- want to draw this square using these points, but you want the bottom-left
- corner to be positioned at point (100,200). This requires a form of
- transform known as translation.
-
- Nonzero values of the lM31 and lM32 elements in the transformation
- matrix cause an image to be shifted or translated from one place to
- another without any distortion of the image:
-
- │ 1.0 0.0 0 │
- │ │
- │ 0.0 1.0 0 │
- │ │
- │ lM31 lM32 1 │
-
- The transformation formulas are:
-
- x' = x + lM31
- y' = y + lM32
-
- The matrix translates the point (0,0) to the point (lM31,lM32). If the
- matrix represents the default viewing transform, then the point (0,0)
- in model coordinate space is the same as the point (lM31,lM32) in page
- coordinate space.
-
- TRANSFRM.C uses translation in order to set the model space origin to
- the center of the window. During the processing of the WM_SIZE message in
- the client window procedure, TRANSFRM sets the default viewing
- transformation matrix so that the lM31 field is half the width of the
- client window in page units and lM32 is half the height. Because the
- WM_SIZE message reports the size of a window in pixels, TRANSFRM must use
- the GpiConvert function to convert the center position from device space
- to page space.
-
- Changing the position of the figure relative to the ruler can be done by
- entering different values of lM31 and lM32 in the TRANSFRM dialog box.
- Figure 13 shows the result when lM31 equals 150 and lM32 equals ──50.
-
- The figure is shifted right by 150 units (1.5 inches) and down by 50 units
- (0.5 inch). As you can see, translation will always transform a square to
- another square. The object is simply moved to a different location on the
- output device.
-
-
- Scaling
-
- The fxM11 and fxM22 fields of the MATRIXLF structure are the "scaling"
- fields. If fxM11 is greater than 1.0, then the resultant image is
- expanded along the x axis. Values less than 1.0 cause the width of the
- image to be compressed. Similarly, fxM22 affects the height of the image.
-
- If all other fields of the matrix are set to their default values, the
- transformation matrix for scaling is
-
- │ fxM11 0.0 0 │
- │ │
- │ 0.0 fxM22 0 │
- │ │
- │ 0 0 1 │
-
- and the transformation formulas are:
-
- x' = (fxM11) x
- y' = (fxM22) y
-
- In general, scaling causes a square to be transformed into a rectangle.
- Figure 14 shows TRANSFRM with fxM11 set to 3.0 and fxM22 set to 0.5.\
-
-
- Reflection
-
- Reflection is a form of scaling that uses negative scaling factors.
- Negative values of the fxM11 and fxM12 fields cause the image to be
- flipped horizontally or vertically.
-
- For example, if you use this transformation matrix
-
- │ -1.0 0.0 0 │
- │ │
- │ 0.0 1.0 0 │
- │ │
- │ 0 0 1 │
-
- then the transformation formulas are:
-
- x' = -x
- y' = y
-
- This reflection flips the image around the vertical axis (see Figure 15).
-
- Similarly, this matrix
-
- │ 1.0 0.0 0 │
- │ │
- │ 0.0 -1.0 0 │
- │ │
- │ 0 0 1 │
-
- causes a reflection around the horizontal axis and turns the object upside
- down (Figure 16).
-
-
- Shear
-
- The shear transform effectively tilts one of the coordinate axes in such
- a way that it is not at right angles to the other axis. The resultant
- drawing is distorted: a square is mapped to a parallelogram.
-
- A nonzero value of fxM21 causes x-shear:
-
- │ 1.0 0.0 0 │
- │ │
- │ fxM21 1.0 0 │
- │ │
- │ 0 0 1 │
-
- The transformation formulas are:
-
- x' = x + (fxM21) y
- y' = y
-
- This shifts a point left or right by an amount dependent upon the distance
- between the point and the horizontal axis. Figure 17 shows the TRANSFRM
- screen when fxM21 is set to 1.0.
-
- Similarly, the fxM12 field causes y-shear. The transformation matrix
- is:
-
- │ 1.0 fxM12 0 │
- │ │
- │ 0.0 1.0 0 │
- │ │
- │ 0 0 1 │
-
- Figure 18 shows y-shear when fxM12 is set to 1.0.
-
- Shear sometimes suggests a three-dimensional view of an object. However,
- because a square is always mapped to a parallelogram, there is no real
- depth perspective.
-
- Now try this matrix, which combines y-shear and negative x-shear:
-
- │ 1.0 1.0 0 │
- │ │
- │ -1.0 1.0 0 │
- │ │
- │ 0 0 1 │
-
- The result appears in Figure 19, in which the image is rotated 45 degrees
- counterclockwise and enlarged somewhat. This suggests that a combination
- of scaling and shear can produce rotation, which is exactly what happens.
-
-
- Rotation
-
- From basic trigonometry we know that to rotate an image a degrees around
- the origin in a counterclockwise rotation requires the following
- formulas:
-
- x' = x * cos(a) ── y * sin(a)
- y' = x * sin(a) +
-
- These formulas fit very nicely into the transformation matrix:
-
- │ cos(a) sin(a) 0 │
- │ │
- │ ─sin(a) cos(a) 0 │
- │ │
- │ 0 0 1 │
-
- For example, to rotate an image 45 degrees counterclockwise around the
- origin, use:
-
- │ 0.707 0.707 0 │
- │ │
- │ ─0.707 0.707 0 │
- │ │
- │ 0 0 1 │
-
- To rotate an image 90 degrees counterclockwise, use:
-
- │ 0.0 1.0 0 │
- │ │
- │ -1.0 0.0 0 │
- │ │
- │ 0 0 1 │
-
- You can experiment with rotation angles in TRANSFRM by selecting the
- "Angle..." button to invoke a second dialog box from the first. This is
- shown in Figure 20.
-
- A rotation of 112.5 degrees and a scaling factor of 2.5 is seen in
- Figure 21.
-
-
- Working Backwards
-
- The TRANSFRM program also allows you to derive a transformation matrix
- by selecting three corners of the box that encloses the stick figure. You
- select the lower-left corner by clicking with the mouse, the lower-right
- corner by clicking with the mouse while holding down the Shift key, and
- the upper-left corner by clicking with the mouse while holding down the
- Ctrl key.
-
-
- Matrix Multiplication
-
- One reason why matrices are used to represent graphics transforms is that
- the cumulative effect of two transforms is equivalent to the product of
- the two matrices.
-
- For example, suppose you want to first double the height of an object and
- then rotate the object 45 degrees counterclockwise. You would multiply
- the two matrices that describe these two transforms, as shown in
- Figure 22.
-
- In the resultant matrix, the element in the ith row and jth column is the
- summation of element-by-element products of the ith row of the first
- matrix and the jth column of the second matrix.
-
- Matrix multiplication is associative but not commutative. If you want to
- first rotate an object 45 degrees and then double the height, the
- composite transformation matrix would be calculated as shown in
- Figure 23.
-
- A combination of matrices is often used with rotation to choose the point
- around which the object is rotated. For example, suppose you have stored
- a series of points that describes an object. You want to rotate this
- object 90 degrees around the point (100,100). This is equivalent to
- translating the object by ──100 units on the x axis and ──100 units on the y
- axis, rotating the image around the origin, then translating it back 100
- units on the x and y axes. The three matrices that describe these
- transforms are multiplied together in sequence as shown in Figure 24.
-
- The result is:
-
- │ 0.0 1.0 0 │
- │ │
- │ ─1.0 0.0 0 │
- │ │
- │ 200 0 1 │
-
- This is the same as rotating the image 90 degrees and then translating
- it 200 units along the x axis.
-
-
- Combining Transforms
-
- You do not have to do the matrix multiplication in your program. GPI can
- do the multiplications for you by using the TRANSFORM_ADD and
- TRANSFORM_PREEMPT parameters to such functions as
- SetModelTransformMatrix and SetDefaultViewMatrix.
-
- The use of GPI matrix multiplication to combine three transforms is
- demonstrated in the REVOLVE program shown in Figure 25.
-
- This program does some rudimentary animation using the GPI transforms.
- It shows the stick figure drawn the same way that it was in the TRANSFRM
- program, but subjected to three transforms in the REVOLVE function
- SetModelTransform. The transforms are varied on each WM_TIMER
- message, so the figure moves and changes in shape. The three transforms
- are effectively multiplied together through the use of the parameter
- TRANSFORM_ADD when GpiSetModelTransformMatrix is called for the
- second and third matrices.
-
- The first transform scales the height of the figure by a factor ranging
- between ─1 and 1. (The factor is obtained from a sine function.) This
- gives the figure the appearance of falling head-over-heels. The second
- transform rotates the figure. The third transform scales the figure by
- the same factor on both the x and y axes. First, this scaling factor is
- decreased, which makes the figure look like it's falling away from you;
- then it is increased, which makes the figure look like it's falling toward
- you.
-
-
- Conclusion
-
- The key to creating sophisticated graphics applications is based on the
- ability to understand the interrelationships between coordinate spaces
- and the transforms required to move from one to another. The transforms
- themselves are controlled by transformation formulas and represented by
- transformation matrices. GPI provides a rich set of functions for handling
- transformations, allowing simple programs such as TRANSFRM and REVOLVE
- to create animated sequences. Although GPI was not designed for animation,
- it's nice to discover that it's features can be exploited for such
- purposes.
-
-
- Figure 1: The Cartesian coordinate system that forms the basis of our
- intuitive notion of the GPI coordinate system. But it's not that
- simple.
-
-
- ∙
- +y
- ∙
- ∙
- ∙
- ∙-x ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ +x∙
- ∙
- ∙
- ∙
- -y
- ∙
-
-
-
- Figure 2: A rotated coordinate system.
-
-
- ∙ ∙
- +y +x
- ∙ ∙
- ∙ ∙
- ∙ ∙
- ∙
- ∙ ∙
- ∙ ∙
- ∙ ∙
- -x -y
- ∙ ∙
-
-
-
- Figure 3: A coordinate system in which the two axes are not at right angles.
-
-
- ∙
- +y
- ∙
- ∙
- ∙
- ∙-x ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ +x∙
- ∙
- ∙
- ∙
- -y
- ∙
-
-
-
- Figure 4: The five coordinate spaces in the micro-PS subset of GPI.
- Transforms map from one coordinate space to the next.
-
- ╓┌────────────┌──────┌────────────┌───────────────┌──────────────────────────╖
- Coordinate Units Transform Types of GPI Function to
- Space Transformation Set the Transform
- ┌─────────────────────┐
- │World Arbitrary│
- └───────────────┬─────┘
- │ ┌───────────────────────────────────────────────────────┐
- └─│Modeling Translation │
- │ Scaling GpiSetModelTransformMatrix│
- │ Rotation │
- │ Shear │
- └─────┬─────────────────────────────────────────────────┘
- ┌─────────────────────┐ │
- │Model Arbitrary│─┘
- └───────────────┬─────┘
- │ ┌───────────────────────────────────────────────────────┐
- └─│Default Translation │
- │Viewing Scaling GpiSetModelTransformMatrix│
- │ Rotation │
- │ Shear │
- Coordinate Units Transform Types of GPI Function to
- Space Transformation Set the Transform
- │ Shear │
- └─────┬─────────────────────────────────────────────────┘
- ┌─────────────────────┐ │
- │Page Metrics, │─┘
- │ Pixels, or│
- │ arbitrary│
- └───────────────┬─────┘
- │ ┌──────────────────────────────────────────────────────┐
- └─│Device Translation GpiCreatePS and │
- │ Scaling GpiSetPageViewport │
- └─────┬────────────────────────────────────────────────┘
- ┌─────────────────────┐ │
- │ Device Pixels │─┘
- │(window) │
- └───────────────┬─────┘
- │ ┌──────────────────────────────────────────────────────┐
- └─│Windowing Translation (None) │
- │System │
- └─────┬────────────────────────────────────────────────┘
- Coordinate Units Transform Types of GPI Function to
- Space Transformation Set the Transform
- └─────┬────────────────────────────────────────────────┘
- ┌─────────────────────┐ │
- │ Media Pixels │─┘
- │(screen) │
- └─────────────────────┘
-
-
- Figure 5: Initial values of the device transform rectangle after creating a
- presentation space.
-
- Device Transform
- RECTL Field Initial Value
-
- rcl.xLeft 0
- rcl.yBottom 0
- rcl.xRight Page width in pixels ──1
- rcl.yTop Page height in pixels ──1
-
-
- Figure 6: Device Transformation Formulas
-
- rcl.xRight - rcl.xLeft + 1
- x = x * ──────────────────────────── + rcl.xLeft
- device page sizl.cx
-
- rcl.yTop - rcl.yBottom + 1
- y = y * ──────────────────────────── + rcl.yBottom
- device page sizl.cy
-
-
- Figure 7: Reduced Device Transformation Formulas
-
- Page width in pixels
- x = x * ────────────────────────
- device page Page width in page units
-
- Page height in pixels
- y = y * ─────────────────────────
- device page Page height in page units
-
-
- Figure 8: Examples of FIXED Values
-
- FIXED Value Interpretation
-
- 0x1 1/65536
- 0x2000 1/8
- 0x10000 1
- 0xAC000 10 3/4
- 0xFFF8F000 ─7 1/16
-
-
- Figure 9: GPI Uses Matrix Multiplication to Convert Points
-
- │ fxM11 fxM12 0 │
- │ │
- │ │
- │ x y 1 │ * │ fxM21 fxM22 0 │ = │ x' y' 1 │
- │ │
- │ │
- │ lM31 lM32 1 │
-
-
- Figure 10: Source Code for the TRANSFRM Program
-
- TRANSFRM Make File
-
- #--------------------
- # TRANSFRM make file
- #--------------------
-
- transfrm.obj : transfrm.c transfrm.h
- cl -c -G2sw -W3 -Zp transfrm.c
-
- transfrm.res : transfrm.rc transfrm.h
- rc -r transfrm
-
- transfrm.exe : transfrm.obj transfrm.def transfrm.res
- link transfrm, /align:16, NUL, os2, transfrm
- rc transfrm.res
-
- TRANSFRM.C Source Code
-
- /*-------------------------------------------
- TRANSFRM.C -- Demonstrates GPI Transforms
- -------------------------------------------*/
-
- #define INCL_WIN
- #define INCL_GPI
-
- #include <os2.h