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 #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 #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 #include #include #include #include #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 #include #include #include #include #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