Microsoft Systems Journal Volume 3

────────────────────────────────────────────────────────────────────────────

Vol. 3 No. 1 Table of Contents

────────────────────────────────────────────────────────────────────────────

Preparing for Presentation Manager: Paradox(R) Steps Up to Windows 2.0

Ansa Software is developing versions of the Paradox data base product for
the 80386, OS/2, UNIX(R)/XENIX(R), and Microsoft(R) Windows environments,
all largely sharing the same code. This article explains how Ansa ported
their large MS-DOS(R) application to the Windows environment for one of
the new versions.


Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager

Microsoft OS/2 Presentation Manager offers the best of the Microsoft Windows
operating environment and the protected-mode OS/2 operating system. The
sample program SayWhat highlights the similarities and differences between
Presentation Manager and Windows programming techniques.


Programming Considerations In Porting To Microsoft(R) XENIX(R) System
V/386

XENIX System V/386, Microsoft's version of UNIX for the 386, supports
virtual memory, demand paging, and both 286 and 386 mode execution. This
article outlines considerations that help programmers choose between 16- and
32-bit program models when developing applications for XENIX V/386.


HEXCALC: An Instructive Pop-Up Calculator For Microsoft(R) Windows

HEXCALC, a pop-up window hexadecimal calculator, explores a method of using
dialog box templates to define the layout of child window controls on a
program's main window, a technique illustrating features of both Microsoft
Windows Version 2.0 and the OS/2 Presentation Manager.


Effectively Using Far and Huge Data Pointers In Your Microsoft(R)
C Programs

Programs that use "huge" pointers to access very large amounts of data are
often affected by the expense of using relatively slow 32-bit arithmetic.
Under certain conditions, huge pointers can be converted to far pointers,
resulting in a significant decrease in access time.

EMS Support Improves Microsoft(R) Windows 2.0 Application Performance

Windows 2.0 uses expanded memory to bank-switch Windows applications,
allowing multiple large programs to run simultaneously as if each were
the only application running.The article examines how Windows' exploitation
of LIM 4.0 expanded memory support can affect your Windows application.


EDITOR'S NOTE

There has been much speculation about the amount of effort required to move
applications from the Windows 2.0 environment to the OS/2 Presentation
Manager. With varying estimates and opinions ringing in our ears, we decided
to devote a major part of this issue to exploring what's involved in
programming for the Presentation Manager.

We turned to Michael Geary, who spent a great deal of the last year
exploring Presentation Manager. He provides us with a step-by-step guide to
moving a Windows 2.0 application to Presentation Manager. To illustrate the
differences, we have includeded side-by-side the complete code listings of
both versions of his tutorial program.

Charles Petzold, inveterate Windows expert, also provides insight on the
topic with parallel Microsoft(R) Windows 2.0 and Presentation Manager
versions of his instructive HEXCALC program.

Ansa Software has had notable success with the MS-DOS(R) version of its
Paradox(R) database. Currently under development are Paradox versions for
Microsoft Windows, OS/2 Presentation Manager, UNIX(R) 5.3 and XENIX(R),
as well as protected-mode 80386. We talked with Richard Schwartz, vice
president of software development and cofounder of Ansa, to discover how one
software firm is preparing for their move to Presentation Manager.

With all the hoopla surrounding the OS/2 systems, we don't want to forget
the importance of recent XENIX and MS-DOS developments. In particular, the
new version of XENIX for the 386 environments has generated much interest,
including questions on the portability between different versions of XENIX.
Martin Dunsmuir, who is responsible for XENIX development at Microsoft,
answers these questions and others.

We look forward to 1988, and the new developments this coming year will
bring. Look for MSJ to be there with the details.──ED.


Masthead

JONATHAN D. LAZARUS
Editor and Publisher

EDITORIAL

TONY RIZZO
Technical Editor

CHRISTINA G.DYAR
Associate Editor

JOANNE STEINHART
Production Editor

GERALD CARNEY
Staff Editor

KIM HOROWITZ
Editorial Assistant

ART

MICHAEL LONGACRE
Art Director

VALERIE MYERS
Associate Art Director

CIRCULATION

WILLIAM B. GRANBERG
Circulation Manager

L. PERRIN TOMICH
Assistant to the Publisher

DONNA PUIZINA
Administrative Assistant

Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
in part or in whole without permission is prohibited.

Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
William Neukom, Secretary.

Microsoft Corporation assumes no liability for any damages resulting from
the use of the information contained herein.

Microsoft, MS-DOS, XENIX, CodeView and the Microsoft logo are registered
trademarks of Microsoft Corporation. PageMaker is a registered trademark of
Aldus Corporation. Paradox is a registered trademark of Ansa Software, a
Borland Company. Apple and Macintosh are registered trademarks of Apple
Computer, Inc. UNIX is a registered trademark of American Telephone and
Telegraph Company. RAMpage! is a registered trademark of AST Research, Inc.
Advantage! is a trademark of AST Research, Inc. IBM and PC/AT are registered
trademarks of International Business Machines Corporation. PS/2 and Micro
Channel are trademarks of International Business Machines Corporation. Intel
is a registered trademark of Intel Corporation. Above is a trademark of
Intel Corporation. Lotus and 1-2-3 are registered trademarks of Lotus
Development Company. Microrim and R:BASE are registered trademarks of
Microrim, Inc.

████████████████████████████████████████████████████████████████████████████

Preparing for Presentation Manager: Paradox Steps Up to Windows 2.0

───────────────────────────────────────────────────────────────────────────
Also see the related article:
 Paradox Under Microsoft(R) OS/2
───────────────────────────────────────────────────────────────────────────

Craig Stinson☼

Ansa Software, the Belmont, Calif., firm recently acquired by Borland
International, sells only one product, but that product, the relational
database manager Paradox(R), will appear in four new versions during the
coming year. A fifth new version will probably be available in early 1989.

The offspring are aimed at different operating environments:
Microsoft(R) Windows Version 2.0, OS/2 systems Version 1.0, the OS/2
Presentation Manager, UNIX(R) 5.3 and XENIX(R), and the 80386 running MS-
DOS(R) 3.x in protected mode with the help of a 386 MS-DOS compatibility
tool. Paradox 386 was shown at last November's Comdex and will probably be
available by the time you read this article; it won't require a
compatibility tool, just a 386 machine. The OS/2 1.0 version should be
released during the first quarter of 1988, the Windows version in the
second quarter, the UNIX/XENIX version in the third quarter, and the
Presentation Manager version in about a year, depending on the date of the
OS/2 Presentation Manager's arrival.

All Paradox versions, old and new, will have multiuser capability and will
be data-compatible. With versions for all the major personal computing
environments (except Macintosh(R)──Ansa is currently evaluating the
feasibility of a Mac version), Ansa will be in a position to serve the vast
majority of desktop users. And all users at various nodes of a network will
be able to access and edit a common database, regardless of the hardware or
operating systems they're running.


Sound Design Pays Off

What type of effort is involved in porting a large, complex MS-DOS
application such as Paradox to all these different operating environments?
In a recent interview with MSJ, Richard Schwartz, vice president for
software development and a cofounder of Ansa, said that the move from one
character-based system to another has been relatively straightforward,
thanks largely to the foresight of the original designers of Paradox.

"We designed Paradox from the beginning to be very clean and portable," says
Schwartz. "We tried not to play dirty tricks getting at the operating system
or creating hardware dependencies." This is not to say, of course, that the
original Paradox doesn't write directly to the screen. "We do, of course,
but we've isolated it. We have one module that will access the screen memory
map directly. And even the BIOS dependencies are all in one place."

The move from a character environment (MS-DOS 3.x) to a graphics environment
(Microsoft Windows and the OS/2 Presentation Manager) is a considerably
more ambitious undertaking. Schwartz talked about the whys and hows of
this move and about how the graphics-based Paradox will look, feel, and
perform relative to the original.


Why Windows?

The move to Windows now, according to Schwartz, will give Ansa advantages in
marketing and in implementation when the OS/2 Presentation Manager arrives.

"We think it's important to put out the Windows version, not because Windows
2.0 is going to be the primary market-certainly, Presentation Manager
market will dwarf the Windows market-but because a lot of companies will be
evaluating the Presentation Manager interface through Windows 2.0. And
applications that run under Windows early on will have an advantage, given
the long evaluation cycles that companies go through."

Second, because Windows 2.0 is here now and Presentation Manager is not,
the effort required to rethink and recode Paradox for Windows is the best
preparation Ansa can undertake for the eventual port to the Presentation
Manager-even though the API for Presentation Manager will differ
significantly from that for Windows.


The Good News

How hard was the port to Windows? According to Schwartz, all but the
underlying database engine was redesigned, borrowing pieces of the
previous architecture and source code. Nevertheless, he asserts, about 50
percent of the original source code emigrated to the new world intact.
That's an off-the-cuff, work-in-progress estimate, but even if it is too
optimistic, it is still good news for Windows developers: not all programs
will have to be rewritten from the ground up to run in the new graphics
environments.

Moreover, in Ansa's case the conversion will probably take about two person-
years of work. That's not bad, considering that the original Paradox was a
14-person-year effort. And the company has done it without hiring a lot of
specialized expertise. That's also good news, considering the current
scarcity and high market value of Macintosh and Windows programmers.

"We tried to get some people with product-level graphics experience. We
used a headhunter for a while, but we were very unsuccessful," says
Schwartz. "And it turned out that we did very well taking good software
engineers who have general computer science sophistication and having them
come up to speed under Windows. We found that general computer science
background was the most important factor."


A Natural Fit

Making Ansa's transition from characters to pixels easier was the fact that
Paradox, despite its early origins (development for the first version
began in the summer of 1981), has always been a highly visual, interactive
program. From an interface design standpoint, therefore, the transfer to
Windows entailed an expansion of ideas already in place rather than a
wholesale reconceptualization.

"We've always had a very visual orientation, an object orientation," says
Schwartz. "And it was just sort of a natural fit."

Data tables in the character-based Paradox, for example, are presented to
the user in the form of visual constructs that look much like windows,
except that the borders are made of text-graphic characters and don't go all
the way around (see Figure 1☼). Like a frame in Framework or a screenful of a
spreadsheet, these objects act as portholes onto larger expanses of data,
which Paradox calls images.

Paradox presents the user with a workspace, much as Windows does, on which
he can keep several images visible at once. And though it doesn't support a
mouse, the character-based product lets the user manipulate the sizes and
locations of images, and the width of columns within them, by positioning
a highlight in the right hot spot and pressing cursor keys. This ability to
interact immediately with a data construct, instead of having to issue a
menu command and describe what one wants, is what Schwartz calls "direct
operability," an important component of the original Paradox interface.


Visual Interaction

"The idea was──as much as possible, anywhere we could──to allow visual
interaction, rather than descriptive interaction," says Schwartz. "Instead
of asking the user to explain or describe something, we asked him to show it
to us. That's very compatible with moving to the Windows environment."

Paradox under Windows will look quite similar at first glance to the
original version (see Figure 2☼), except that the Lotus-style menu gives way
to drop-down menus, the text comes up black-on-white, and the images are
fully formed document windows, with title bars, scroll bars, control menus,
and sizing icons. The direct operability will still be there, but there'll
be more of it. And, of course, there will be mouse support.

One new area of direct operability in Windows Paradox is in the
specification of sorts. In the character-based version, the user indicated
which fields he wanted to be sort keys and in which direction he wanted a
field sorted by typing a field number followed by an "a" for ascending or a
"d" for descending (see Figure 3☼).

The Windows version (see Figure 4☼) simplifies this process and makes it less
analytical by letting the user double-click directly on the name of each
sort key field. An arrow indicating sort direction (ascending, by default)
will appear to the left of the chosen field name; to switch directions, the
user merely toggles the arrow by double-clicking on it.

The notion of clicking on a value to toggle it or to select from available
values appears elsewhere in Windows Paradox. In creating or restructuring a
table, for example, where the user is asked for a data type, he can click
the mouse to get a menu showing available types. Similarly, in the
create/restructure subsystem, a double-click on a field name will summon
a dialog box, allowing the user to inspect current validity-check
definitions or create new ones.


Implementation Advantages

Describing the conversion effort, Schwartz downplays the fearsome Windows
learning curve, emphasizing instead the implementation advantages
conferred by the graphics environment. In particular, he cites the greater
degree of independence among the various objects with which the user
interacts.

In the Windows environment, every table in the workspace, as well as every
form, query, and report specification, lives in its own window and has its
own message handler. That message handler is responsible for knowing only
about itself, how to repaint itself and how to update itself. In the
character-based version, more attention had to be paid to the "global
state," that is, to the ways in which changes in a table would affect
everything else in the workspace.

"At the implementation level," says Schwartz, "that means that your code
can have an architecture such that everything is purely local to each
image. So you can have a local handler that knows how to resize a window or
how much of it to repaint. If anything in the size of that window is
affected, Windows takes care of sending messages to the other windows to let
them know that some other portion needs to be repainted."


Debugging Difficulties

According to Schwartz, one fairly serious hindrance to the development of
Windows Paradox was the lack of debugging tools for use in the Windows
environment.

"It's a harder problem anyway," Schwartz says, "because there's more going
behind the scenes in the Windows environment." The movability and
discardability of Windows object code complicates matters, making bugs
harder to find. Bugs tend to turn up later in the game as the result of
stress on the system because a piece of code has been moved in memory.

The current version of CodeView(R) doesn't work in the Windows environment,
though Microsoft is working on a Windows-specific version. Symdeb is
provided, but Ansa didn't use it extensively, preferring instead to rely on
algorithmic-level debugging by embedding printf statements to test values
of variables at various points in the program.

"In our design environment, we have a monochrome display hooked up as well
as an EGA. We've hooked things up so that we can put printfs in the code and
have them scroll out on the monochrome display. You get the effect of
traditional debugging preserved in the Windows environment."

Making debugging even more difficult was the fact that Ansa was working with
early prerelease versions of Windows and the Windows development tools (the
project began last April). Programmers were sometimes hard pressed to tell
which problems they should attribute to their own code, which to the
compiler, and which to bugs in the underlying environment.


User Advantages

The independence of screen objects provides several benefits for users.
Most importantly, it means that users can have more things going on at
once and still keep track of everything. Instead of being limited to the
number of images that will fit one after the other on screen, the user can
load up the screen with overlapping windows, thereby achieving whatever
level of information density he finds most appropriate.

This facilitates such activities as moving from one table to another,
copying data or structures from one table to another, discovering
correlations between one set of data and another, and so on. It also means
that Windows Paradox will appear somewhat less "modal" to users than its
text-based ancestor. There are places in the current program where a user
has to drop one context, if only briefly, to work in another. The Windows
version will minimize the effects of such breaks in continuity.

For example, in the character-based version, if the user wants to modify a
data-entry form on the fly while entering data, he issues a command to
summon the forms subsystem. The data and form he was working on disappear
at that point, becoming visible again only after he's finished changing
the form spec. In the Windows version, the form subsystem will appear in a
child window, with the data remaining visible in the parent window.

Other benefits the Windows Paradox user will find include the following:

 ■  The Windows version will automatically support any high-resolution
    large-screen displays supported by Windows itself.

 ■  The report subsystem will offer enhanced font support and will accept
    bit-mapped images by way of the Windows clipboard. Users can dress up
    Paradox reports with company logos, signatures, clip art, and the
    like. Windows Paradox will not accept bit-mapped images as data
    types, although Schwartz concedes that that's "a natural direction"
    for the product to evolve in.

 ■  Windows Paradox can exchange data with other Windows applications by
    way of dynamic data exchange (DDE). At the Microsoft Excel
    announcement, Ansa showed an early version of Paradox running as a
    client of Microsoft Excel, with Paradox started up by means of the
    macro language and transferring data to Microsoft Excel via DDE (see
    Figure 5☼). The finished Windows version of Paradox will allow a
    reversal of this scenario: you can start up and control other
    applications by means of scripts written in the Paradox application
    language (PAL).


Performance Hits

The picture sounds rosy, but Schwartz concedes that the advantages of the
Windows environment will exact a few performance penalties. The hits, he
suggests, will come less from Windows' need to represent text in graphics
mode than from its propensity to swap program modules in and out of memory.

Because the program was still under development at this writing, Ansa was
not prepared to supply benchmark data. However, Schwartz maintains the
company is "quite happy with the Paradox performance on 10-MHz 286s and
above." And, he says, the SMARTDrive program, a Microsoft-supplied disk-
caching utility tailored to enhance the performance of Windows, speeds
things up significantly on all machines.


Fears Allayed

What messages would Ansa's Schwartz pass along to another Windows
application developer? The most obvious one is that the redesign of an MS-
DOS program to run under Windows is not the horrendous task it's commonly
thought to be, particularly if the program was well designed in the first
place. A second notion involves the maintenance of individuality under
conditions that seem to impose similarity.

"Some people say that all products are going to look alike, that all the
creativity has been taken away from the designer in the Windows
environment," says Schwartz. "So much has been standardized that
everything's going to look the same and work the same. That's certainly
true at some level. Still, there is a very diverse set of ways in which one
can take advantage of the environment to present a particular application.

"You need to concern yourself first with the core model of how an
application is to be constructed-with the user-level concepts and how you
want to present them. And only after that you look at what the Windows tools
are and how you'll use them. The greatest effect on the user is the
underlying model, and that is not affected at all by Windows. Windows is
just a way to present that, to communicate it more effectively to the
user."


───────────────────────────────────────────────────────────────────────────
Paradox Under Microsoft(R) OS/2
───────────────────────────────────────────────────────────────────────────

Paradox was concieved in the days of very limited resources. Development
began in the first half of 1981, months before the announcement of the
IBM(R) PC, when 64Kb was as much memory as anyone could expect to have.

Over the next several years, as larger machines grew commonplace, Paradox
grew in scope and ambition, so that by the time the the product was
announced in the Fall of 1885, it required 512Kb to run──practically
speaking, that meant a 640Kb computer.

However, like a depression era parent, Paradox never forgat what it was like
to live in lean times. From the beggining, the product incorporated a
virtual memory manager that swapped data as necessary to the hard disk. One
of the programs selling points, moreover, was the heuristic approach to the
processing of relational queries. An important aspect of this process was
machine reasoning about what data was currently in memory and what was
swapped out somewhere.

Therefore, the large address space afforded by the 80286 and 80386 chips
running in protected mode represents a liberation for Paradox, demonstrated
by the preliminary statistics shown in the accompanying charts.

Judging by the numbers, Paradox running under the OS/2 systems in protected
mode should take care of complex queries and large scale sorts much more
quickly than the MS-DOS 3.3 version. The 80386 version will do queries about
as efficiently as the OS/2 versions and will be even more nimble at sorting.

Perhaps the most surprosing information revealed by these early numbers is
that Paradox 2.0 running in real mode under OS/2 (in the OS/2 compatibility
box) outperforms the same product running on the same hardware under MS-DOS
3.3. According to Microsoft, most MS-DOS programs will run 5 percent slower
in the compatibility box. Ansa's Richard Schwartz attributes the improvement
to the fact that Paradox is doing a lot of random file access and is
therefore able to profit significantly from OS/'s disk caching.

Performance statistics for the Microsoft Windows version of Paradox were not
yet available as of this writing.


   ╔═══╗
   ║   ║100─┐      93
   ║   ║    │      ▄▄▄                     ▒▒ Query ░░ Sort
   ║   ║    │     ░░░█
   ║   ║ 80─┤  73 ░░░█            71
   ║ S ║    │  ▄▄▄░░░█            ▄▄▄
   ║ E ║    │ ▒▒▒█░░░█        59 ░░░█            60
   ║ C ║ 60─┤ ▒▒▒█░░░█        ▄▄▄░░░█        46  ▄▄▄        48
   ║ O ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█        ▄▄▄░░░█        ▄▄▄ 44
   ║ N ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█ ▄▄▄
   ║ D ║ 40─┤ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ║ S ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ║   ║ 20─┤ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
   ╚═══╝  0─┼─▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀─┐
            Paradox 2.0    Paradox 2.0     Paradox OS/2   Paradox 386
           under DOS 3.3   in the OS/2     in protected   under DOS 3.3
                         Compatability Box     mode

   ════════════════════ ELAPSED TIME IN SECONDS ═══════════════════════

Preliminary performance statistics for three new versions of Paradox, as
measured on a PS/2 Model 80. The query involved a join of a 5000-record
table and a 10,000-record table; the sort was for the 500-record table. The
figures for Paradox OS/2 and Paradox 386 may differ when those products are
shipped.

───────────────────────────────────────────────────────────────────────────

  ╔═P═╗
  ║ E ║    60─┐                                              53%
  ║ R ║       │     ▒▒ Query                                 ▄▄▄▄
  ║ F ║       │                                             ░░░░█
  ║ O ║    50─┤     ░░ Sort                                 ░░░░█
  ║ R ║       │                                             ░░░░█
  ║ M ║       │                      37%                    ░░░░█
  ║ A ║    40─┤                      ▄▄▄▄ 35%           34% ░░░░█
  ║ N ║       │                     ▒▒▒▒█ ▄▄▄▄          ▄▄▄▄░░░░█
  ║ C ║       │                     ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ E ║    30─┤        24%          ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║   ║       │        ▄▄▄▄         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ I ║       │   19% ░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ M ║    20─┤   ▄▄▄▄░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ P ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ R ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ O ║    10─┤  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ V ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ E ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  ║ M ║     0─┴──▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀──┐
  ╚═T═╝      Paradox 2.0 in the   Paradox OS/2 in    Paradox 386 under
             OS/2 compatability   protected mode     DOS 3.3
             box

Performance improvements of three new versions of Paradox over Paradox 2.0
running in MS-DOS 3.3. These figures are preliminary and may not reflect
actual performance when the products are shipped.

████████████████████████████████████████████████████████████████████████████

Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager

Micheal Geary☼

The Microsoft(R) OS/2 Presentation Manager represents the marriage of two
technologies: the Windows operating environment and the protected-mode OS/2
operating systems. In any wedding, it's traditional to have something old,
something new, something borrowed, and something blue, and Presentation
Manager is no exception.

Something old is Microsoft Windows. In a sense, Presentation Manager is
really Windows. Although there have been a number of changes from the real-
mode (MS-DOS(R)) version of Windows, nearly all Windows programming concepts
carry over directly to the new environment: message-driven programming,
window functions, resources, and so forth. If you know how to program
Windows, Presentation Manager will be the icing on the wedding cake.

Something new is the protected-mode hardware and OS/2 operating systems. It
would not be far from the truth to say this is the environment Windows was
really meant for. OS/2 offers true preemptive multitasking, unlike the
"round-robin" multitasking of real-mode Windows. Applications no longer have
to wait for each other to yield control. Within a single application, you
can make good use of OS/2's multiple threads of execution, assigning to
different windows their own threads. The protected-mode hardware greatly
expands memory addressing capability and eliminates the need for some of the
software memory management done by real-mode Windows.

Something borrowed is the new Graphics Programming Interface (GPI). Adapted
from the mainframe graphics GDDM (Graphics Data Display Manager) and GCP
(Graphics Control Program) standards, GPI replaces Windows' Graphics Device
Interface (GDI) with a richer, more versatile set of graphics functions.

Something blue, as you've already guessed, is IBM. Presentation Manager
plays a crucial role in IBM's Systems Application Architecture (SAA) as the
user interface portion of SAA, at least for machines with graphics
capabilities. IBM has an ambitious goal for SAA: a common user interface and
programming interface on a wide range of machines, from PCs to mainframes.
Eventually you should be able to take a Presentation Manager application and
compile it to run on any machine. For machines without graphics, a subset of
Presentation Manager functions will be provided, giving a similar user
interface, but running in a character mode environment.


Presentation Manager Programming

To explore the differences and the similarities between a Presentation
Manager application and a Windows application, let's look at a sample
program called SayWhat, which is my entry in the "silliest Windows
application" contest. I started out with the Hello program that comes with
the Windows Software Development Kit and doctored it up a bit. Instead of
just displaying the "hello" message in one place, the text wanders around
the window randomly, changing its color as it moves. If you make SayWhat
into an icon, it spells out the message one letter at a time, with the
letter wandering around the icon. A control panel, a modeless dialog box,
lets you change the displayed text and adjust how fast it moves and how
often it selects a new random direction to move in.

───────────────────────────────────────────────────────────────────────────
The source code for SayWhat is found at the end of the text
───────────────────────────────────────────────────────────────────────────

SayWhat illustrates several programming techniques that are useful for
Windows and Presentation Manager. It also points out an irritating flaw in
the way many Windows applications do their screen painting and how to
improve it. A Windows version and a Presentation Manager version of SayWhat
are listed side by side for comparison.

When comparing the listings, it is immediately apparent that the overall
structure of the two programs is identical. The most important aspects of
Windows apply equally to Presentation Manager: messages, window functions,
painting, and so on. However, at the detailed coding level we will see many
differences.


Getting Started

At the beginning, both programs have a main function, but in the
Presentation Manager application it's called main( ) instead of WinMain( )
as in the Windows application. That's because a Presentation Manager
application starts out as an ordinary OS/2 application; what makes it
different is its use of the Presentation Manager API.

The parameters passed to main are different from those that are passed to
WinMain in the Windows version. The lpszCmdLine parameter is replaced by the
more conventional, and convenient, argc and argv parameters. There is no
nCmdShow parameter. If you create your main application window as an
ordinary visible window by using the WinCreateStdWindow function, this is
taken care of for you along with the window size. If you require more direct
control over your window placement, create your main window as invisible and
then set the size and position yourself. Finally, the hPrevInstance and
hInstance parameters are missing: hPrevInstance isn't used at all and there
is nothing in Presentation Manager corresponding to the GetInstanceData
function. Under OS/2, there is no such thing as an instance in the same
sense as there is in Windows. There are only OS/2 processes, and each
process initializes its own data. Subsequent instances of an application
have exactly the same initialization as the first instance.


Message Loop

The main message loop of a Presentation Manager program is nearly identical
to the main loop of a Windows program. Other than some name changes and the
use of the hAB parameter, the only difference is that the TranslateMessage
function is not used. Since this function was always required anyway, it has
been merged with WinGetMsg. Actually, the keyboard messages under
Presentation Manager have been substantially simplified. The messages
WM_KEYDOWN, along with WM_KEYUP, WM_SYSKEYDOWN, and WM_SYSKEYUP have all
been removed, and the information that they provided is incorporated into
WM_CHAR. The WM_CHAR message now provides the "raw" keyboard information
that the WM_KEYDOWN/UP messages used to give, as well as the "translated"
ASCII character code always provided by WM_CHAR.


Initialization

Before the message loop starts up, we have to initialize the application and
create our main window, which is done in the SayInitApp function. The
Presentation Manager version begins with two new calls: first, it calls
WinInitialize which initializes the program for Presentation Manager and
returns a handle to an anchor block, called hAB in our code. The anchor
block isn't really used by Presentation Manager, but it's there for
compatibility with future SAA environments. However, the hAB parameter is
required on several of the Presentation Manager function calls.

WinInitialize doesn't actually make the application become a Presentation
Manager application. In fact, kernel applications can call WinInitialize if
they want to use the local memory management or atom functions provided with
Presentation Manager. Then it calls WinCreateMsgQueue, which allocates its
message queue. Unlike Windows, in Presentation Manager you have to do this
yourself. This gives you more flexibility; you can specify the size of the
message queue or just take the default by specifying a size of 0, as SayWhat
does.

WinCreateMsgQueue is that special call that says "yes, this is a
Presentation Manager application." It establishes this thread of execution
as a message queue thread, and if you happen to start up a Presentation
Manager application on a system that has the regular kernel shell running
instead of the Presentation Manager shell, this is the call that puts you
into graphics mode. If you have multiple threads, some of the threads may
have message queues and some may not, but any thread that wants to create
windows must call WinCreateMsgQueue since threads cannot share a queue.

Both applications then load some string resources. This is nearly identical
in the two versions, except for the hAB parameter used in the Presentation
Manager version. Next we pick up display information: the number of colors
available, the system font size, and the screen size. In the Windows
version, you create an information context which is like a device context,
but is used only for gathering information about the device. Then you pick
up information on the system font with GetTextMetrics, and get the number of
display planes with a GetDeviceCaps call.

The Presentation Manager version is a little different. Instead of an
information context or device context, it gets a presentation space with the
WinGetPS call. Although Presentation Manager also has device contexts, most
graphics functions use a presentation space instead, which, in a simple
application like SayWhat, can be used much as a device context would be in
the Windows version. However, the presentation space is much more powerful
and versatile; for example, it can record and later play back graphics
calls, much like a metafile. You could avoid having to process WM_PAINT
messages this way. (This approach does not enhance performance, but it can
be a programming convenience for some applications.) The details of
presentation spaces are not discussed here, since SayWhat doesn't do
anything fancy with them.

Having obtained the presentation space handle, the Presentation Manager
version then calls GpiQueryCharBox to get the system font character size and
GpiQueryColorData to pick up the number of colors. Note that we're using the
total number of distinct solid colors here, not the number of display planes
as in the Windows version.


Creating a Window

Now you can register your window class and create the main window. It is
here that you encounter a major difference between Windows and Presentation
Manager-the architecture of a standard application window.

Under Windows, a standard application window has two main components: the
client area, which your code normally deals with, and the nonclient area,
which includes all the controls around the edge of the window; title bar,
sizing border, menu bar, and so forth. Your window function receives
messages for the client and nonclient areas, but you usually pass the
WM_NCxxxxx nonclient messages through to DefWindowProc. Although this works
for most applications, this architecture has some problems. The behavior of
all of the nonclient items is hard-coded into Windows. It is possible to
override some of this behavior by intercepting the WM_NCxxxxx messages, but
this is fairly cumbersome and very limited. There are also some strange
inconsistencies, like the fact that standard scroll bars are part of the
special nonclient area, but any non-standard scroll bars are created as
child windows and are treated differently.

When I was coding the window editor in one of my applications, this
client/nonclient architecture was a serious obstacle. I needed complete
control over all aspects of a window's behavior, because my application lets
the user build and edit window characteristics on the fly. In design mode, I
didn't want the nonclient items to behave normally, since they were objects
being edited at that point. I finally got things to work reasonably well,
but not quite the way I wanted. For example, if the user wished to remove
the Maximize icon from a window, I would have to destroy and recreate the
entire window.

Under Presentation Manager, this client/nonclient architecture has been
eliminated. The functionality is still there, but child windows and some new
predefined window classes implement it. All of the items that were part of
the nonclient area are now child windows, and you can do special things with
them simply by subclassing them as you would any other window. There is a
new window class, the frame window, which acts as a container for all these
child windows and has the smarts to position them appropriately according to
the window size. What used to be the client area under Windows is now itself
a child window of the frame window.

Now, for many applications, all of this won't make much difference. You can
use the WinCreateStdWindow function to set up a standard frame window with
the desired controls around the border, and things will work pretty much as
before. The only significant difference is that some operations that were
performed with Windows function calls are now done by sending messages to
the various controls.

However, in an application where the window frame controls are treated
specially, this new architecture makes life much easier. Subclassing a child
window is a lot more convenient than trying to fool all of the WM_NCxxxxx
messages; it's a simpler, more consistent architecture. It is even
reasonable to add more special controls to the window frame, placed anywhere
you want.

There's another, more subtle area where the window architecture has been
cleaned up. Under Windows, every window logically has a parent window and an
owner window. The parent determines how screen space is used-a child window
is contained within its parent and clipped at the edges of the parent. The
owner determines two things; control windows (edit controls, for example)
send notification messages to their owner, and pop-up windows disappear when
their owner is minimized. These are two different steps, although Windows
combines them into a single "parent" window handle and interprets this
handle according to the window style bits. For a WS_CHILD window, the
"parent" is actually both the parent and the owner. For a WS_POPUP, the
actual parent is NULL, and the "parent" is really the owner.

Presentation Manager separates the parent and owner window handles, letting
you specify them individually. This removes the need for the WS_CHILD,
WS_POPUP, and WS_OVERLAPPED window styles; instead, a window is just a
window. Furthermore, every window has a parent, because there is one new
window, called the desktop window, which occupies the entire screen. Every
top level window is actually a child of the desktop window.

With all this in mind, let's look at the WinRegisterClass and
WinCreateStdWindow calls in SayWhat. You'll see that there isn't much to the
WinRegisterClass call. The WNDCLASS structure from the Windows version is
gone, because most of what it specified has migrated to the frame window
class and also to WinCreateStdWindow. One interesting new option in
WinRegisterClass is the CS_SYNCPAINT style bit. If this bit is set, then
WM_PAINT messages for windows of this class are not deferred in the
customary fashion. Instead, WM_PAINT is sent immediately whenever the window
becomes invalidated. This is how the window frame controls get repainted
immediately; they have the CS_SYNCPAINT style. In the Windows version, this
is handled specially by WM_PAINT, but under Presentation Manager, this
feature is available to any window. Since SayWhat's window painting
procedure is very simple and quite fast, CS_SYNCPAINT makes for a nice clean
display. A window class that takes longer to paint should omit this style
bit, and its windows will receive deferred WM_PAINT messages just as they do
under Windows.

The WinCreateStdWindow call creates SayWhat's frame window, along with its
client window and all the frame controls. The frame window handle and the
client window handle are returned by this call, the latter through the
pointer in the last parameter. The window style bits look pretty familiar,
but many of them have FS_ names instead of WS_ names because they are really
just indicators to the frame window telling it which child windows to
create. Also, there's no size or position information in this call. The
trick is that if you give the window the WS_VISIBLE style, it will
automatically be assigned the appropriate default size and position.
Alternatively, you can create it as invisible and place it where you want
(and then make it visible) explicitly with the call WinSetWindowPos.

WinCreateStdWindow is just a convenience for creating standard frame
windows. There is also a generic WinCreateWindow that can create any kind of
window, giving you total control over how the window is set up. You would
use this function to create additional child windows or any other kind of
special window.


Window Functions

Now that our window has been created, it's time to look at the window
function, SayWhatWndProc. You'll notice that the window functions in the two
versions are very similar. As I mentioned before, all of the keyboard input
is done under a WM_CHAR message, not under WM_KEYDOWN as in the Windows
version, but otherwise it's essentially the same. Mouse input is nearly
identical also, except that the message names have been changed to avoid the
inconsistency of the right button sending a message called WM_LBUTTONUP/DOWN
when the buttons have been swapped-the main button is instead WM_BUTTON1.

The other messages processed by SayWhatWndProc are also extremely similar;
WM_CREATE, WM_DESTROY, WM_SIZE, WM_PAINT, WM_TIMER, and WM_COMMAND. One very
apparent difference is that there is no corresponding wParem = = SIZEICONIC,
but there is a new WM_MINMAX message that provides the same information.
WM_MINMAX is thus intercepted and processed to find out when the window is
being minimized or restored to the normal state.

There are some differences in the message parameters for all messages,
though. Instead of a 16-bit parameter and a 32-bit parameter, wParam and
lParam, there are two 32-bit parameters; lParam1 and lParam2. This allows a
little more information to be passed along with messages, but the real
reason for this change was for those messages that pass an additional window
handle in wParam, such as WM_VSCROLLCLIPBOARD. Under Presentation Manager,
window handles and most other handles are 32 bits, not 16 bits, so the
wParam had to be expanded to permit this. The hWnd parameter to the window
function is also a 32-bit parameter. Watch out for this one if you have any
code that assumes handles are 16 bits.


Window Painting

The SayWhatPaint function handles window painting in both versions. It is
rather similar in the two versions, although the Presentation Manager
version has no PAINTSTRUCT. Instead, the WinBeginPaint function returns a
presentation space handle and gives you the update rectangle as a separate
parameter. In addition, it makes use of GpiSetColor instead of using
SetTextColor, and uses GpiCharStringAt instead of TextOut. The same work is
being performed in both cases.

There's a little twist in SayWhat's painting logic in both versions. When
you run SayWhat, you will notice that the text wanders around the window
cleanly. There is no flicker, even though the text is repainted from scratch
each time. Many Windows applications have a little bit of flicker when they
paint into their window, which is caused by the usual practice of erasing
the background completely and then painting the nonblank portions. That's
fine when you paint a window for the first time, but if you want to repaint
a portion of the window, there is a split second where the background is
blanked out.

You've probably seen this effect running Notepad or Write. If you type
quickly in either program, the line you are on will flicker a little instead
of painting cleanly. In SayWhat, you can see the same problem by selecting
the Flicker option on the control dialog box. It's even worse here because
the window gets repainted so frequently.

How does SayWhat avoid this flicker in its normal mode? Simple: it doesn't
erase the background underneath the text it's about to paint. It does erase
any other portions of the background if needed, but where the text itself
will go, it just writes the text without erasing first. This works because
text is normally written opaquely; it completely covers whatever is
underneath it.

There are two different painting routines to illustrate this in
SayWhatPaint. If the bCleanPaint variable is FALSE, it uses the common,
flickery painting method, that is, it first erases the entire background and
then draws the text. If bCleanPaint is TRUE, it draws the text and erases
only the remaining portions of the background.

It may seem silly to get worked up about this, but fixing it dramatically
improves the appearance of an application. I know this from my own
experience: the outline editor in my own application originally had a lot of
irritating flicker, which went away when I converted it to use the clean
painting method. And, even though it took a little extra code in the paint
function, it actually reduced the overall amount of code and complexity. The
flicker had been so annoying that I was ready to put in all kinds of special
cases to make sure I never invalidated more of the window than was
necessary, but with the clean painting method, it no longer matters.
Painting is totally invisible to the user now, so if I invalidate a little
too much, no one is the wiser.


Dialog Boxes

SayWhat has two dialog boxes: a modal About box and a modeless control
panel, which are both created under the WM_COMMAND case in SayWhatWndProc.
The code in both versions is similar except that MakeProcInstance is not
necessary in the protected-mode environment. OS/2 and the memory management
hardware set up the proper data segment for "call-back" functions such as
dialog functions. This is a blessing for anyone who has struggled with
mysterious system crashes caused by leaving out a MakeProcInstance under
Windows. However, you still need to remember to EXPORT your call back
functions and window functions, as was the case before.

There is one important difference in the implementation of the dialog box
functions themselves. Under Windows, a dialog box function is not coded like
a normal window function. In fact, the actual window function for all dialog
boxes is a private function inside Windows called DlgWndProc, which calls
your dialog function before doing its own message switch statement and if it
returns nonzero, it skips its own message processing. One problem with this
approach is that it's a little confusing to have dialog functions work
differently from normal window functions. Another problem is that it is
impossible for a dialog function to return a value back to a SendMessage
call. The return value you give isn't returned back through SendMessage;
DlgWndProc simply returns zero. (Odd as it may seem, DlgWndProc treats the
WM_CTLCOLOR message as a special case and does return the value you
provide.)

Under Presentation Manager, a dialog function operates like any other window
function. Its return value is the real return value, and there is a default
message function, WinDefDlgProc, that you call for messages that you don't
process yourself. SayAboutDlgProc illustrates this difference and, like most
About boxes, doesn't do much else except close itself when you hit the OK
button.

The other dialog box, SayWhat's control panel, is a little more interesting.
This dialog box has no owner window (parent window under Windows). Usually,
you specify an owner window when you create a dialog box, which causes three
things to happen: the box is automatically positioned relative to the
parent, it is always on top of the parent, and it disappears if the parent
is made into an icon. However, in SayWhat, I didn't want the latter two
actions; I wanted the box to remain visible if SayWhat's window itself is
made into an icon so that you could fiddle with the parameters while the
icon is displayed. I also wanted to be able to put SayWhat's main window on
top of the dialog box.

Creating the dialog box with a NULL owner takes care of this; however, I
still wanted the box to be positioned relative to the main window instead of
being relative to the screen. So, the WM_INITDIALOG code does this
positioning explicitly, using ClientToScreen calls in the Windows version
and a WinMapWindowPoints call in the Presentation Manager version.
WinMapWindowPoints is a handy function, which replaces ClientToScreen and
ScreenToClient, since you can specify a source window, a destination window,
or both. It will also map as many points as you want in one call. In this
program it maps two points, that is, one rectangle.

After the mapping, you have to make sure that you haven't moved the dialog
box partially offscreen, so you check it against the screen boundaries and
adjust it back if necessary. Then, you call SetWindowPos or WinSetWindowPos
to set the new position. This function works a little differently under
Presentation Manager. If you have used SetWindowPos in Windows 2.0, you know
it can do a number of things at once, such as move the window, resize it, or
change the window ordering. The default is for it to do all of these, and in
the last parameter you tell it what you do not want it to do.
WinSetWindowPos does the same, except the last parameter tells it what you
do want it to do-just the opposite.

Note that the y parameter of WinSetWindowPos takes the window bottom, not
the window top. This shows one subtle difference between Windows and
Presentation Manager: vertical coordinates run from bottom to top, not top
to bottom, that is, position (0,0) is the lower-left corner of a window or
the screen, not the upper-left corner. To be consistent with this, a
rectangle structure is now stored in the order (left, bottom, right, top)
instead of (left, top, right, bottom). If you're familiar with the various
GDI mapping modes available in Windows, you have probably noticed that they
all run from bottom to top except for MM_TEXT, which runs from top to
bottom. This change in Presentation Manager removes that inconsistency──all
coordinates normally run the same direction now──but it obviously requires
some conversion effort. You can instruct Presentation Manager to use the
top-to-bottom coordinates within a window, so that could help in converting
existing applications.

After positioning the dialog box, the WM_INITDIALOG code initializes its
control windows. One item of interest here is the scroll bar initialization
in SayInitBar. The Windows version calls two scroll bar functions,
SetScrollRange and SetScrollPos, to initialize the scroll bar. Presentation
Manager deals with these two at the same time, with a single
SBM_SETSCROLLBAR message that sets the position and range. Also, you will
find throughout Presentation Manager that this is performed with a message
instead of a special function; many of the special-purpose functions for
control windows have been removed in favor of messages. If you prefer the
approach of calling functions, you can always write your own equivalent
functions that send the appropriate messages.

The other interesting messages in SayWhatDlgProc are WM_COMMAND and
WM_HSCROLL. The latter message gets sent for activity in either of the
dialog box's scroll bars, and it calls SayDoBarMsg to set the new scroll bar
position and to set the corresponding edit control value. However, in
Presentation Manager this message passes you the child window ID of the
scroll bar control instead of the window handle. This is convenient, since
you need the window ID to tell which scroll bar you're dealing with. The
WM_COMMAND message is sent when the user clicks any of the pushbuttons or
radio buttons in the dialog box. (It also gets sent when you type in one of
the edit controls, but SayWhat ignores it.) The WM_COMMAND processing is
nearly identical in the two versions, except that the IsDlgButtonChecked
function call is replaced with a BM_QUERYCHECK message in Presentation
Manager.


Conclusion

As you can see, converting a Windows application to Presentation Manager
isn't trivial, but it is pretty straightforward. The similarities between
the two systems far outweigh the differences. If you're fluent in Windows
programming, you've already done the hard part, and you know how different a
Windows application is from a conventional MS-DOS or OS/2 program. If you
haven't gotten started with Windows yet, well.... What's that I hear in the
distance? Yes, it's wedding bells. Don't be late!

───────────────────────────────────────────────────────────────────────────
Source code in this article is referenced W for Windows programs and PM for
Presentation Manager programs.
───────────────────────────────────────────────────────────────────────────


Figure 1W:  SAYWHAT is the MAKE File for SAYWHAT

#  SAYWHAT
#  MAKE file for SAYWHAT (Windows version)

sw.obj:  sw.c  sw.h
   cl -c -AS -DLINT_ARGS -Gcsw -Oas -W3 -Zdp sw.c

sw.res:  sw.rc  sw.h
   rc -r sw.rc

saywhat.exe:  sw.obj  sw.res  sw.def
   link4 sw, saywhat/align:16, saywhat/map/line, slibw, sw.def
   mapsym saywhat
   rc sw.res saywhat.exe


Figure 1PM:  SAYWHAT is the MAKE File for SAYWHAT

# SAYWHATP
# MAKE file for SAYWHAT (Presentation Manager version)

swp.obj:    swp.c  swp.h
   cl -c -AS -DLINT_ARGS -G2csw -Oat -W3 -Zp swp.c

swp.res:    swp.rc  swp.h
   rc -r swp.rc

saywhatp.exe:  swp.obj  swp.res  swp.def
   link @swp.lnk
   mapsym saywhat
   rc swp.res saywhat.exe


Figure 2W:

Note that there is no Windows equivalent for SAYWHATP.LNK, the LINK file
for the PM version of SAYWHAT.


Figure 2PM:  SWP.LNK is the Link File for SAYWHAT

SWP.LNK

swp
saywhatp/align:16
saywhatp/map
wincalls doscalls mlibc286/NOD
swp.def


Figure 3W:  SW.DEF is the Module Definition File for SAYWHAT

; SW.DEF
; SW.DEF - Module definition file for SAYWHAT (Windows version)


NAME    SayWhat


DESCRIPTION 'Say What!'


STUB    'WINSTUB.EXE'

CODE    MOVEABLE
DATA    MOVEABLE MULTIPLE

HEAPSIZE  128
STACKSIZE 4096

EXPORTS
   SayAboutDlgProc     @1
   SayWhatDlgProc      @2
   SayWhatWndProc      @3


Figure 3PM:  SWP.DEF is the Module Definition File for SAYWHAT

; SWP.DEF
; SWP.DEF - Module definition file for SAYWHAT (PM version)

NAME    SayWhat

DESCRIPTION 'Say What!'

STUB    'OS2STUB.EXE'

CODE    MOVEABLE
DATA    MOVEABLE MULTIPLE

HEAPSIZE  128
STACKSIZE 8192

EXPORTS
   SayAboutDlgProc     @1
   SayWhatDlgProc      @2
   SayWhatWndProc      @3


Figure 4W:  SW.RC is the Resource File for SAYWHAT

SW.RC

#include <style.h>
#include "sw.h"

STRINGTABLE
BEGIN
   STR_NAME,    "SayWhat!"
   STR_TITLE,   "Say What!"
   STR_WHAT,    "Hello Windows!"
END

SayWhat!    MENU
BEGIN

 POPUP "&Say"
 BEGIN
   MENUITEM "&What..."                 , CMD_WHAT
   MENUITEM SEPARATOR
   MENUITEM "E&xit"                    , CMD_EXIT
   MENUITEM SEPARATOR
   MENUITEM "A&bout SayWhat!..."       , CMD_ABOUT
 END

END

DLG_ABOUT DIALOG 19, 17, 130, 83
STYLE WS_DLGFRAME | WS_POPUP
BEGIN
 CTEXT "Microsoft Windows",   -1,  0,  8, 127,  8
 CTEXT "Say What!",           -1,  0, 18, 127,  8
 CTEXT "Version 1.00",        -1,  0, 30, 127,  8
 CTEXT "By Michael Geary",    -1,  0, 44, 129,  8
 DEFPUSHBUTTON "Ok",        IDOK, 48, 62,  32, 14
END

DLG_WHAT DIALOG 49, 41, 177, 103
CAPTION "Say What!"
STYLE WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_POPUP
BEGIN
CONTROL "Say &What:"   -1,            "Static",    SS_LEFT  | WS_GROUP,
CONTROL ""             ITEM_WHAT,     "Edit",      ES_LEFT  | ES_AUTOHSCROLL
CONTROL "&Time Delay:" -1,            "Static",    SS_LEFT,
CONTROL ""             ITEM_INTBAR,   "ScrollBar", SBS_HORZ | WS_TABSTOP,
CONTROL ""             ITEM_INTERVAL, "Edit",      ES_LEFT  | WS_BORDER | WS
CONTROL "&Stability:"  -1,            "Static",    SS_LEFT,
CONTROL ""             ITEM_DISTBAR,  "ScrollBar", SBS_HORZ | WS_TABSTOP,
CONTROL ""             ITEM_DISTANCE, "Edit",      ES_LEFT | WS_BORDER | WS_
CONTROL "Painting:"    -1,            "Static",    SS_LEFT,
CONTROL "&Clean"       ITEM_CLEAN,    "Button",    BS_AUTORADIOBUTTON | WS_G
CONTROL "&Flicker"     ITEM_FLICKER,  "Button",    BS_AUTORADIOBUTTON,
CONTROL "Enter"        IDOK,          "Button",    BS_DEFPUSHBUTTON | WS_GRO
CONTROL "Esc=Close"    IDCANCEL,      "Button",    BS_PUSHBUTTON | WS_TABSTO
END


Figure 4PM:  SWP.RC is the Resource File for SAYWHAT

SWP.RC

#include <style.h>
#include "sw.h"

STRINGTABLE
{
 STR_NAME,    "SayWhat!"
 STR_TITLE,   "Say What!"
 STR_WHAT,    "Hello Windows!"
}

MENU SayWhat!
{
 SUBMENU "~Say",                  -1
 {
   MENUITEM "~What...",           CMD_WHAT
   MENUITEM SEPARATOR
   MENUITEM "E~xit",              CMD_EXIT
   MENUITEM SEPARATOR
   MENUITEM "A~bout SayWhat!...", CMD_ABOUT
 }
}

DLGTEMPLATE DLG_ABOUT
{
 DIALOG "", DLG_ABOUT, 19, 17, 130, 83, FS_DLGBORDER |
WS_SAVEBITS | WS_VISIBLE
 {
   CTEXT "Microsoft Windows",   -1,  0,  8, 127,  8
   CTEXT "Say What!",           -1,  0, 18, 127,  8
   CTEXT "Version 1.00",        -1,  0, 30, 127,  8
   CTEXT "By Michael Geary",    -1,  0, 44, 129,  8
   DEFPUSHBUTTON "Ok",        IDOK, 48, 62,  32, 14
 }
}

DLGTEMPLATE DLG_WHAT
{
 DIALOG "Say What!", DLG_WHAT, 49, 41, 177, 103,
FS_TITLEBAR | FS_SYSMENU | FS_BORDER | WS_VISIBLE
 {
 CONTROL "Say ~What:"   -1,              8, 10,  40,  8, WC_STATIC,    SS_LE
 CONTROL ""             ITEM_WHAT,      56,  8, 112, 12, WC_EDIT,      ES_LE
 CONTROL "~Time Delay:" -1,              8, 28,  53,  9, WC_STATIC,    SS_LE
 CONTROL ""             ITEM_INTBAR,    56, 28,  88,  8, WC_SCROLLBAR, SBS_H
 CONTROL ""             ITEM_INTERVAL, 152, 26,  16, 12, WC_EDIT,      ES_LE
 CONTROL "~Stability:"  -1,              8, 46,  56,  9, WC_STATIC,    SS_LE
 CONTROL ""             ITEM_DISTBAR,   56, 46,  88,  8, WC_SCROLLBAR, SBS_H
 CONTROL ""             ITEM_DISTANCE, 152, 44,  16, 12, WC_EDIT,      ES_LE
 CONTROL "Painting:"    -1,              8, 64,  40,  8, WC_STATIC,    SS_LE
 CONTROL "~Clean"       ITEM_CLEAN,     56, 62,  36, 12, WC_BUTTON,    BS_AU
 CONTROL "~Flicker"     ITEM_FLICKER,   96, 62,  42, 12, WC_BUTTON,    BS_AU
 CONTROL "Enter"        IDOK,           24, 82,  48, 14, WC_BUTTON,    BS_DE
 CONTROL "Esc=Close"    IDCANCEL,      104, 82,  48, 14, WC_BUTTON,    BS_PU
 }
}


Figure 5W:  SW.H is the C Header file for SAYWHAT

/* SW.H */
/* SW.H - C header file for SAYWHAT (Windows version) */

/* String table constants */

#define STR_NAME        101
#define STR_TITLE       102
#define STR_WHAT        103

/* Menu command IDs */

#define CMD_ABOUT       201
#define CMD_EXIT        202
#define CMD_WHAT        203

/* Dialog box resource IDs */

#define DLG_ABOUT       301
#define DLG_WHAT        302

/* 'What...' dialog box item IDs */

#define ITEM_WHAT       401
#define ITEM_INTBAR     402
#define ITEM_INTERVAL   403
#define ITEM_DISTBAR    404
#define ITEM_DISTANCE   405
#define ITEM_CLEAN      406
#define ITEM_FLICKER    407

/* Timer IDs */

#define TIMER_MOVE      501
#define TIMER_CHAR      502


Figure 5PM:  SWP.H is the C Header File for SAYWHAT

/* SWP.H */
/* SWP.H - C header file for SAYWHAT (PM version) */

/* String table constants */

#define STR_NAME        101
#define STR_TITLE       102
#define STR_WHAT        103

/* Menu command IDs */

#define CMD_ABOUT       201
#define CMD_EXIT        202
#define CMD_WHAT        203

/* Dialog box resource IDs */

#define DLG_ABOUT       301
#define DLG_WHAT        302

/* 'What...' dialog box item IDs */

#define ITEM_WHAT       401
#define ITEM_INTBAR     402
#define ITEM_INTERVAL   403

#define ITEM_DISTBAR    404
#define ITEM_DISTANCE   405
#define ITEM_CLEAN      406
#define ITEM_FLICKER    407

/* Timer IDs */

#define TIMER_MOVE      501
#define TIMER_CHAR      502

/* Menu resource IDs */

#define MENU_WHAT       601


Figure 6W:  SW.C is the C Source Code Listing for SAYWHAT

/* SW.C
* SW.C - C code for SayWhat - Windows version
*/

/* Note - this code *must* be compiled with the -Gc switch */

#ifndef LINT_ARGS
#define LINT_ARGS  /* turn on argument checking for C runtime */
#endif

#include <windows.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "sw.h"

#define MIN_INTERVAL    1       /* limits for nInterval */
#define MAX_INTERVAL    999

#define MIN_DISTANCE    1       /* limits for nDistance */
#define MAX_DISTANCE    99

/*  Static variables  */

HANDLE  hInstance;              /* application instance handle */

HWND    hWndWhat;               /* main window handle */
HWND    hWndPanel;              /* contral panel dialog handle */

FARPROC lpfnDlgProc;            /* ProcInstance for dialog */

char    szAppName[10];          /* window class name */
char    szTitle[15];            /* main window title */

char    szText[40];             /* current "what" text */
NPSTR   pText = szText;         /* ptr into szText for icon mode */
char    cBlank = ' ';           /* a blank we can point to */

RECT    rcText;                 /* current text rectangle */
POINT   ptAdvance;              /* increments for SayAdvanceText */
POINT   ptCharSize;             /* X and Y size of a character */
POINT   ptScreenSize;           /* X and Y size of the screen */

int     nDisplayPlanes;         /* number of display planes */
DWORD   rgbTextColor;           /* current text color */
HBRUSH  hbrBkgd;                /* brush for erasing background */

int     nInterval = 40;         /* current "Interval" setting */
int     nDistance = 30;         /* current "Distance" setting */
int     nDistLeft = 0;          /* change direction when hits 0 */
BOOL    bCleanPaint = TRUE;     /* clean or flickery painting? */
BOOL    bMouseDown = FALSE;     /* is mouse down? */
BOOL    bIconic = FALSE;        /* is main window iconic? */

/*  Full prototypes for our functions to get type checking  */

BOOL FAR SayAboutDlgProc( HWND, unsigned, WORD, LONG );
void     SayAdvanceTextChar( HWND );
void     SayAdvanceTextPos( HWND );
void     SayChangeColor( HWND );
void     SayDoBarMsg( HWND, HWND, WORD, int );
void     SayFillRect( HDC, int, int, int, int );
void     SayInitBar( HWND, int, int, int, int );
BOOL     SayInitApp( HANDLE, int );
void     SayInvalidateText( HWND );
void     SayLimitTextPos( HWND );
void     SayMoveText( HWND, POINT );
void     SaySetBar( HWND, int *, int );
void     SayExitApp( int );
BOOL FAR SayWhatDlgProc( HWND, unsigned, WORD, LONG );
void     SayWhatPaint( HWND );
LONG FAR SayWhatWndProc( HWND, unsigned, WORD, LONG );
void     WinMain( HANDLE, HANDLE, LPSTR, int );

/*  Dialog function for the "About" box  */

BOOL FAR SayAboutDlgProc( hWndDlg, wMsg, wParam, lParam )
   HWND        hWndDlg;
   unsigned    wMsg;
   WORD        wParam;
   LONG        lParam;
{
   switch( wMsg )
   {
     case WM_COMMAND:
       switch( wParam )
       {
         case IDOK:
           EndDialog( hWndDlg, TRUE );
           return TRUE;
       }
   }
   return FALSE;
}

/*  Advances to next display character in iconic mode.
*  Forces in a blank when it reaches the end of string.
*/

void SayAdvanceTextChar( hWnd )
   HWND        hWnd;
{
   if( ! bIconic )
       return;

   if( pText = = &cBlank )
       pText = szText;
   else if( ! *(++pText) )
       pText = &cBlank;

   SayChangeColor( hWnd );
   SayInvalidateText( hWnd );
}

/*  Advances text position according to ptAdvance.  Decrements
*  nDistLeft first, and when it reaches zero, sets a new
*  randomized ptAdvance and nDistLeft, also changes color.
*  Does nothing if mouse is down, so text will track mouse.
*/

void SayAdvanceTextPos( hWnd )
   HWND        hWnd;
{
   int         i;

   if( bMouseDown )
       return;

   SayInvalidateText( hWnd );

   if( nDistLeft- < 0 ) {
       nDistLeft = rand() % nDistance + 1;
       do {
           i = rand();
           ptAdvance.x = (
               i < SHRT_MAX/3   ? -1
             : i < SHRT_MAX/3*2 ?  0
             :                     1
           );
           i = rand();
           ptAdvance.y = (
               i < SHRT_MAX/3   ? -1
             : i < SHRT_MAX/3*2 ?  0
             :                     1
           );
       } while( ptAdvance.x = = 0  &&  ptAdvance.y = = 0 );

       if( ! bIconic )
           SayChangeColor( hWnd );
   } else {
       rcText.left   += ptAdvance.x;
       rcText.right  += ptAdvance.x;
       rcText.top    += ptAdvance.y;
       rcText.bottom += ptAdvance.y;
   }

   SayInvalidateText( hWnd );
}

/*  Changes color to a random selection, if color is available.
*  Forces a color change - if the random selection is the same
*  as the old one, it tries again.
*/

void SayChangeColor( hWnd )
   HWND        hWnd;
{
   HDC         hDC;
   DWORD       rgbNew;
   DWORD       rgbWindow;

   if( nDisplayPlanes <= 1 ) {
       rgbTextColor = GetSysColor(COLOR_WINDOWTEXT);
   } else {
       rgbWindow = GetSysColor(COLOR_WINDOW);
       hDC = GetDC( hWnd );
       do {
           rgbNew = GetNearestColor(
               hDC,
               MAKELONG( rand(), rand() ) & 0x00FFFFFFL
           );
       } while( rgbNew = = rgbWindow || rgbNew = = rgbTextColor );
       rgbTextColor = rgbNew;
       ReleaseDC( hWnd, hDC );
   }
}

/*  Handles scroll bar messages from the control dialog box.
*  Adjusts scroll bar position, taking its limits into account,
*  copies the scroll bar value into the adjacent edit control,
*  then sets the nDistance or nInterval variable appropriately.
*/

void SayDoBarMsg( hWndDlg, hWndBar, wCode, nThumb )
   HWND        hWndDlg;
   HWND        hWndBar;
   WORD        wCode;
   int         nThumb;
{
   int         nPos;
   int         nOldPos;
   int         nMin;
   int         nMax;
   int         idBar;

   idBar = GetWindowWord( hWndBar, GWW_ID );

   nOldPos = nPos = GetScrollPos( hWndBar, SB_CTL );
   GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );

   switch( wCode )
   {
       case SB_LINEUP:         -nPos;              break;

       case SB_LINEDOWN:       ++nPos;             break;

       case SB_PAGEUP:         nPos -= 10;         break;

       case SB_PAGEDOWN:       nPos += 10;         break;

       case SB_THUMBPOSITION:
       case SB_THUMBTRACK:     nPos = nThumb;      break;

       case SB_TOP:            nPos = nMin;        break;

       case SB_BOTTOM:         nPos = nMax;        break;
   }

   if( nPos < nMin )
       nPos = nMin;

   if( nPos > nMax )
       nPos = nMax;

   if( nPos = = nOldPos )
       return;

   SetScrollPos( hWndBar, SB_CTL, nPos, TRUE );

   SetDlgItemInt( hWndDlg, idBar+1, nPos, FALSE );

   switch( idBar )
   {
     case ITEM_DISTBAR:
       nDistance = nPos;
       break;

     case ITEM_INTBAR:
       KillTimer( hWndWhat, TIMER_MOVE );
       nInterval = nPos;
       SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
       InvalidateRect( hWndWhat, NULL, FALSE );
       break;
   }
}

/*  Terminates the application, freeing up allocated resources.
*  Note that this function does NOT return to the caller, but
*  exits the program.
*/

void SayExitApp( nRet )
   int         nRet;
{
   if( GetModuleUsage(hInstance) = = 1 ) {
       DeleteObject( hbrBkgd );
   }

   exit( nRet );
}

/*  Fills a specified rectangle with the background color.
*  Checks that the rectangle is non-empty first.
*/

void SayFillRect( hDC, nLeft, nTop, nRight, nBottom )
   HDC         hDC;
   int         nLeft;
   int         nTop;
   int         nRight;
   int         nBottom;

{
   RECT        rcFill;

   if( nLeft >= nRight  ||  nTop >= nBottom )
       return;

   SetRect( &rcFill, nLeft, nTop, nRight, nBottom );

   FillRect( hDC, &rcFill, hbrBkgd );
}

/*  Initializes the application.  */

BOOL SayInitApp( hPrevInstance, nCmdShow )

   HANDLE      hPrevInstance;
   int         nCmdShow;
{
   WNDCLASS    Class;
   HDC         hDC;
   TEXTMETRIC  Metrics;

   LoadString(
       hInstance, STR_NAME,  szAppName, sizeof(szAppName)
   );
   LoadString(
       hInstance, STR_TITLE, szTitle,   sizeof(szTitle)
   );
   LoadString(
       hInstance, STR_WHAT,  szText,    sizeof(szText)
   );

   hDC = CreateIC( "DISPLAY", NULL, NULL, NULL );
   GetTextMetrics( hDC, &Metrics );
   nDisplayPlanes = GetDeviceCaps( hDC, PLANES );
   DeleteDC( hDC );

   ptCharSize.x = Metrics.tmMaxCharWidth;
   ptCharSize.y = Metrics.tmHeight;

   ptScreenSize.x = GetSystemMetrics(SM_CXSCREEN);
   ptScreenSize.y = GetSystemMetrics(SM_CYSCREEN);

   if( ! hPrevInstance ) {

       hbrBkgd = CreateSolidBrush( GetSysColor(COLOR_WINDOW) );

       Class.style         = 0; /* CS_HREDRAW | CS_VREDRAW; */
       Class.lpfnWndProc   = SayWhatWndProc;
       Class.cbClsExtra    = 0;
       Class.cbWndExtra    = 0;
       Class.hInstance     = hInstance;
       Class.hIcon         = NULL;
       Class.hCursor       = LoadCursor( NULL, IDC_ARROW );
       Class.hbrBackground = COLOR_WINDOW + 1;
       Class.lpszMenuName  = szAppName;
       Class.lpszClassName = szAppName;

       if( ! RegisterClass( &Class ) )
           return FALSE;

   } else {

       GetInstanceData(
           hPrevInstance, (NPSTR)&hbrBkgd, sizeof(hbrBkgd)
       );
   }

   hWndWhat = CreateWindow(
       szAppName,
       szTitle,
       WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT, 0,
       CW_USEDEFAULT, 0,
       (HWND)NULL,
       (HMENU)NULL,
       hInstance,
       (LPSTR)NULL
   );

   if( ! hWndWhat )
       return FALSE;

   ShowWindow( hWndWhat, nCmdShow );
   UpdateWindow( hWndWhat );

   return TRUE;
}

/*  Initializes one scroll bar in the control dialog.  */

void SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
   HWND        hWndDlg;
   int         idBar;
   int         nValue;

   int         nMin;
   int         nMax;
{
   HWND        hWndBar;

   hWndBar = GetDlgItem( hWndDlg, idBar );

   SetScrollRange( hWndBar, SB_CTL, nMin, nMax, FALSE );
   SetScrollPos( hWndBar, SB_CTL, nValue, FALSE );

   SetDlgItemInt( hWndDlg, idBar+1, nValue, FALSE );
}

/*  Invalidates the text within the main window, adjusting the
*  text rectangle if it's gone out of bounds.
*/

void SayInvalidateText( hWnd )
   HWND        hWnd;
{
   SayLimitTextPos( hWnd );
   InvalidateRect( hWnd, &rcText, FALSE );
}

/*  Checks the text position against the window client area
*  rectangle.  If it's moved off the window in any direction,
*  forces it back inside, and also reverses the ptAdvance value
*  for that direction so it will "bounce" off the edge.  Handles
*  both the iconic and open window cases.
*/

void SayLimitTextPos( hWnd )
   HWND        hWnd;
{
   RECT        rcClient;
   POINT       ptTextSize;

   ptTextSize = ptCharSize;

   if( ! bIconic ) {
       pText = szText;
       ptTextSize.x *= strlen(szText);
   }

   GetClientRect( hWndWhat, &rcClient );

   if( rcText.left > rcClient.right - ptTextSize.x ) {
       rcText.left = rcClient.right - ptTextSize.x;
       ptAdvance.x = -ptAdvance.x;
   }

   if( rcText.left < rcClient.left ) {
       rcText.left = rcClient.left;
       ptAdvance.x = -ptAdvance.x;
   }

   if( rcText.top > rcClient.bottom - ptTextSize.y ) {
       rcText.top = rcClient.bottom - ptTextSize.y;
       ptAdvance.y = -ptAdvance.y;
   }


   if( rcText.top < rcClient.top ) {
       rcText.top = rcClient.top;
       ptAdvance.y = -ptAdvance.y;
   }

   rcText.right  = rcText.left + ptTextSize.x;
   rcText.bottom = rcText.top  + ptTextSize.y;
}

/*  Moves the text within the window, by invalidating the old
*  position, adjusting rcText according to ptMove, and then
*  invalidating the new position.
*/

void SayMoveText( hWnd, ptMove )
   HWND        hWnd;
   POINT       ptMove;
{
   SayInvalidateText( hWnd );
   rcText.left = ptMove.x - (rcText.right  - rcText.left >> 1);
   rcText.top  = ptMove.y - (rcText.bottom - rcText.top  >> 1);
   SayInvalidateText( hWnd );
}

/*  Sets one of the dialog scroll bars to *pnValue.  If that
*  value is out of range, limits it to the proper range and
*  forces *pnValue to be within the range as well.
*/

void SaySetBar( hWndDlg, pnValue, idBar )
   HWND        hWndDlg;
   int *       pnValue;
   int         idBar;
{
   HWND        hWndBar;
   int         nMin;
   int         nMax;
   int         nValue;
   BOOL        bOK;

   hWndBar = GetDlgItem( hWndDlg, idBar );

   GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );

   nValue = GetDlgItemInt( hWndDlg, idBar+1, &bOK, FALSE );

   if( bOK  &&  nValue >= nMin  &&  nValue <= nMax ) {
       *pnValue = nValue;
       SetScrollPos( hWndBar, SB_CTL, nValue, TRUE );
   } else {
       SetDlgItemInt(
           hWndDlg,
           idBar+1,
           GetScrollPos( hWndBar, SB_CTL ),
           FALSE
       );
   }
}

/*  Dialog function for the control panel dialog box.  */

BOOL FAR SayWhatDlgProc( hWndDlg, wMsg, wParam, lParam )
   HWND        hWndDlg;
   unsigned    wMsg;
   WORD        wParam;
   LONG        lParam;
{
   HWND        hWndBar;
   RECT        rcWin;
   int         n;

   switch( wMsg )
   {
     case WM_COMMAND:
       switch( wParam )
       {
         case IDOK:
           KillTimer( hWndWhat, TIMER_MOVE );
           GetDlgItemText(
               hWndDlg, ITEM_WHAT, szText, sizeof(szText)
           );
           if( strlen(szText) = = 0 )
               LoadString(
                   hInstance, STR_WHAT, szText, sizeof(szText)
               );
           pText = szText;
           SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
           SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
           SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
           InvalidateRect( hWndWhat, NULL, FALSE );
           return TRUE;

         case IDCANCEL:
           DestroyWindow( hWndDlg );
           return TRUE;

         case ITEM_CLEAN:
         case ITEM_FLICKER:
           bCleanPaint =
               IsDlgButtonChecked( hWndDlg, ITEM_CLEAN );
           return TRUE;
       }
       return FALSE;

     case WM_HSCROLL:
       if( HIWORD(lParam) )
           SayDoBarMsg(
               hWndDlg, HIWORD(lParam), wParam, LOWORD(lParam)
           );
       return TRUE;

     case WM_INITDIALOG:
       GetWindowRect( hWndDlg, &rcWin );
       ClientToScreen( hWndWhat, (LPPOINT)&rcWin.left );
       ClientToScreen( hWndWhat, (LPPOINT)&rcWin.right );
       n = rcWin.right - ptScreenSize.x + ptCharSize.x;
       if( n > 0 )
           rcWin.left -= n;
       rcWin.left &= ~7;  /* byte align */
       n = rcWin.bottom - ptScreenSize.y + ptCharSize.y;
       if( n > 0 )
           rcWin.top -= n;
       SetWindowPos(
           hWndDlg,
           (HWND)NULL,
           rcWin.left,
           rcWin.top,
           0, 0,
           SWP_NOSIZE | SWP_NOZORDER |
               SWP_NOREDRAW | SWP_NOACTIVATE
       );
       SetDlgItemText( hWndDlg, ITEM_WHAT, szText );
       SendDlgItemMessage(
           hWndDlg, ITEM_WHAT,
           EM_LIMITTEXT, sizeof(szText)-1, 0L
       );
       SayInitBar(
           hWndDlg, ITEM_INTBAR,
           nInterval, MIN_INTERVAL, MAX_INTERVAL
       );

       SayInitBar(
           hWndDlg, ITEM_DISTBAR,
           nDistance, MIN_DISTANCE, MAX_DISTANCE
       );
       CheckDlgButton( hWndDlg, ITEM_CLEAN, TRUE );
       return TRUE;

     case WM_NCDESTROY:
       FreeProcInstance( lpfnDlgProc );
       hWndPanel = NULL;
       return FALSE;
   }
   return FALSE;
}

/*  Painting procedure for the main window.  Handles both the
*  clean and flickery painting methods for demonstration
*  purposes.
*/

void SayWhatPaint( hWnd )
   HWND        hWnd;
{
   PAINTSTRUCT ps;

   BeginPaint( hWnd, &ps );

   SetTextColor( ps.hdc, rgbTextColor );

   SayLimitTextPos( hWnd );

   if( bCleanPaint ) {

       /* Clean painting, avoid redundant erasing */

       TextOut(
           ps.hdc,
           rcText.left,
           rcText.top,
           pText,
           bIconic ? 1 : strlen(szText)
       );

       SayFillRect(
           ps.hdc,
           ps.rcPaint.left,
           ps.rcPaint.top,
           rcText.left,
           ps.rcPaint.bottom
       );

       SayFillRect(
           ps.hdc,
           rcText.left,
           ps.rcPaint.top,
           rcText.right,
           rcText.top
       );

       SayFillRect(
           ps.hdc,
           rcText.left,
           rcText.bottom,
           rcText.right,
           ps.rcPaint.bottom
       );

       SayFillRect(
           ps.hdc,
           rcText.right,
           ps.rcPaint.top,
           ps.rcPaint.right,
           ps.rcPaint.bottom
       );

   } else {

       /* Flickery painting, erase background and paint traditionally */

       FillRect( ps.hdc, &ps.rcPaint, hbrBkgd );

       TextOut(
           ps.hdc,
           rcText.left,
           rcText.top,
           pText,
           bIconic ? 1 : strlen(szText)
       );

   }

   EndPaint( hWnd, &ps );

   if( ! nInterval )
       SayAdvanceTextPos( hWnd );
}
/*  Window function for the main window.  */

LONG FAR SayWhatWndProc( hWnd, wMsg, wParam, lParam )
   HWND        hWnd;
   unsigned    wMsg;
   WORD        wParam;
   LONG        lParam;
{
   FARPROC     lpfnAbout;

   switch( wMsg )
   {
     case WM_COMMAND:
       switch( wParam )
       {
         case CMD_ABOUT:
           lpfnAbout =
               MakeProcInstance( SayAboutDlgProc, hInstance );
           DialogBox(
               hInstance, MAKEINTRESOURCE(DLG_ABOUT),
               hWnd, lpfnAbout
           );
           FreeProcInstance( lpfnAbout );
           return 0L;

         case CMD_EXIT:
           DestroyWindow( hWndWhat );
           return 0L;

         case CMD_WHAT:
           if( hWndPanel ) {
               BringWindowToTop( hWndPanel );
           } else {
               lpfnDlgProc =
                   MakeProcInstance( SayWhatDlgProc, hInstance );
               if( ! lpfnDlgProc )
                   return 0L;
               hWndPanel = CreateDialog(
                   hInstance,
                   MAKEINTRESOURCE(DLG_WHAT),
                   (HWND)NULL,
                   lpfnDlgProc
               );
               if( ! hWndPanel )
                   FreeProcInstance( lpfnDlgProc );
           }
       }
       break;

     case WM_CREATE:
       srand( (int)time(NULL) );
       SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
       return 0L;

     case WM_DESTROY:
       if( hWndPanel )
           DestroyWindow( hWndPanel );
       PostQuitMessage( 0 );
       return 0L;

     case WM_KEYDOWN:
       SayInvalidateText( hWnd );
       switch( wParam )
       {
         case VK_LEFT:
           rcText.left -= ptCharSize.x;
           ptAdvance.x  = -1;
           ptAdvance.y  = 0;
           break;

         case VK_RIGHT:
           rcText.left += ptCharSize.x;
           ptAdvance.x  = 1;
           ptAdvance.y  = 0;
           break;

         case VK_UP:
           rcText.top  -= ptCharSize.y >> 1;
           ptAdvance.x  = 0;
           ptAdvance.y  = -1;
           break;

         case VK_DOWN:
           rcText.top  += ptCharSize.y >> 1;
           ptAdvance.x  = 0;
           ptAdvance.y  = 1;
           break;

         default:
           return 0L;
       }
       SayInvalidateText( hWnd );
       nDistLeft = nDistance;
       return 0L;

     case WM_LBUTTONDOWN:
       if( bMouseDown )
           break;
       KillTimer( hWnd, TIMER_MOVE );
       bMouseDown = TRUE;
       SetCapture( hWnd );
       SayMoveText( hWnd, MAKEPOINT(lParam) );
       break;

     case WM_LBUTTONUP:
       if( ! bMouseDown )
           break;
       bMouseDown = FALSE;
       ReleaseCapture();
       SayMoveText( hWnd, MAKEPOINT(lParam) );
       SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
       break;

     case WM_MOUSEMOVE:
       if( bMouseDown )
           SayMoveText( hWnd, MAKEPOINT(lParam) );
       break;

     case WM_PAINT:
       SayWhatPaint( hWnd );
       return 0L;

     case WM_SIZE:
       if( wParam = = SIZEICONIC ) {
           if( ! bIconic )
               SetTimer( hWnd, TIMER_CHAR, 1000, NULL );
           bIconic = TRUE;
       } else {
           if( bIconic )
               KillTimer( hWnd, TIMER_CHAR );
           bIconic = FALSE;
       }
       SayInvalidateText( hWnd );
       nDistLeft = 0;
       SayAdvanceTextPos( hWnd );
       return 0L;

     case WM_TIMER:
       switch( wParam )
       {
         case TIMER_MOVE:
           SayAdvanceTextPos( hWnd );
           break;

         case TIMER_CHAR:
           SayAdvanceTextChar( hWnd );
           break;
       }
       return 0L;
   }
   return DefWindowProc( hWnd, wMsg, wParam, lParam );
}

/*  Main function for the application.  */

void WinMain( hInst, hPrevInst, lpszCmdLine, nCmdShow )
   HANDLE      hInst;
   HANDLE      hPrevInst;
   LPSTR       lpszCmdLine;
   int         nCmdShow;
{
   MSG         msg;

   hInstance = hInst;

   if( ! SayInitApp( hPrevInst, nCmdShow ) )
       SayExitApp( 1 );

   while( GetMessage( &msg, NULL, 0, 0 ) ) {

       if( hWndPanel  &&  IsDialogMessage( hWndPanel, &msg ) )
           continue;

       TranslateMessage( &msg );
       DispatchMessage( &msg );
   }

   SayExitApp( msg.wParam );
}


Figure 6PM:  SWP.C is the C Source Code Listing for SAYWHAT

/*
*  SWP.C - C code for SayWhat - Presentation Manager version
*/

#ifndef LINT_ARGS
#define LINT_ARGS  /* turn on argument checking for C runtime */
#endif

#include <os2pm.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include ╙swp.h╙

#define MIN_INTERVAL    1       /* limits for nInterval */
#define MAX_INTERVAL    999

#define MIN_DISTANCE    1       /* limits for nDistance */
#define MAX_DISTANCE    99

#define COLORDATAMAX    5

/*  Static variables  */

HAB     hAB = NULL;             /* anchor block handle */
HMQ     hMsgQ = NULL;           /* message queue handle */

HWND    hWndWhatFrame;          /* frame window handle */
HWND    hWndWhat;               /* client window handle */
HWND    hWndPanel;              /* control panel dialog handle */

CHAR    szAppName[10];          /* window class name */
CHAR    szTitle[15];            /* main window title */

CHAR    szText[40];             /* current 'what' text */
PSZ     npszText = szText;      /* ptr into szText for icon mode */
CHAR    cBlank = ╒ ╒;           /* a blank we can point to */

RECT    rcText;                 /* current text rectangle */
POINT   ptAdvance;              /* increments for SayAdvanceText */
POINT   ptCharSize;             /* X and Y size of a character */
POINT   ptScreenSize;           /* X and Y size of the screen */

LONG    lColorMax;              /* number of available colors */
LONG    lColor;                 /* current text color index */

SHORT   nInterval = 40;         /* current 'Interval' setting */
SHORT   nDistance = 30;         /* current 'Distance' setting */
SHORT   nDistLeft = 0;          /* change direction when hits 0 */
BOOL    bCleanPaint = TRUE;     /* clean or flickery painting? */
BOOL    bMouseDown = FALSE;     /* is mouse down? */
BOOL    bIconic = FALSE;        /* is main window iconic? */

/*  Full prototypes for our functions to get type checking  */

ULONG FAR PASCAL SayAboutDlgProc( HWND, USHORT, ULONG, ULONG );
VOID             SayAdvanceTextChar( HWND );
VOID             SayAdvanceTextPos( HWND );
VOID             SayChangeColor( HWND );
VOID             SayDoBarMsg( HWND, USHORT, USHORT, SHORT );
VOID             SayExitApp( INT );
VOID             SayFillRect( HPS, SHORT, SHORT, SHORT, SHORT );
VOID             SayInitBar( HWND, SHORT, SHORT, SHORT, SHORT );
BOOL             SayInitApp( VOID );
VOID             SayInvalidateText( HWND );
VOID             SayLimitTextPos( HWND );
VOID             SayMoveText( HWND, POINT );
VOID             SaySetBar( HWND, SHORT *, SHORT );
ULONG FAR PASCAL SayWhatDlgProc( HWND, USHORT, ULONG, ULONG );
VOID             SayWhatPaint( HWND );
ULONG FAR PASCAL SayWhatWndProc( HWND, USHORT, ULONG, ULONG );
void  cdecl      main( INT, PSZ );

/*  Dialog function for the 'About' box  */

ULONG FAR PASCAL SayAboutDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
   HWND        hWndDlg;
   USHORT      wMsg;
   ULONG       lParam1;
   ULONG       lParam2;
{
   switch( wMsg )
   {
     case WM_COMMAND:
       switch( LOUSHORT(lParam1) )
       {
         case IDOK:
           WinDismissDlg( hWndDlg, TRUE );
           break;
       }
   }
   return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
}

/*  Advances to next display character in iconic mode.
*  Forces in a blank when it reaches the end of string.
*/

VOID SayAdvanceTextChar( hWnd )
   HWND        hWnd;
{
   if( ! bIconic )
       return;

   if( npszText = = &cBlank )
       npszText = szText;
   else if( ! *(++npszText) )
       npszText = &cBlank;

   SayChangeColor( hWnd );
   SayInvalidateText( hWnd );
}

/*  Advances text position according to ptAdvance. Decrements
*  nDistLeft first, and when it reaches zero, sets a new
*  randomized ptAdvance and nDistLeft, also changes color.
*  Does nothing if mouse is down, so text will track mouse.
*/

VOID SayAdvanceTextPos( hWnd )
   HWND        hWnd;
{
   SHORT       i;

   if( bMouseDown )
       return;

   SayInvalidateText( hWnd );

   if( nDistLeft- < 0 ) {
       nDistLeft = rand() % nDistance + 1;
       do {
           i = rand();
           ptAdvance.x = (
               i < SHRT_MAX/3   ? -1

             : i < SHRT_MAX/3*2 ?  0
             :                     1
           );
           i = rand();
           ptAdvance.y = (
               i < SHRT_MAX/3   ? -1
             : i < SHRT_MAX/3*2 ?  0
             :                     1
           );
       } while( ptAdvance.x = = 0  &&  ptAdvance.y = = 0 );
       if( ! bIconic )
           SayChangeColor( hWnd );
   } else {
       rcText.xLeft   += ptAdvance.x;
       rcText.xRight  += ptAdvance.x;
       rcText.yTop    += ptAdvance.y;
       rcText.yBottom += ptAdvance.y;
   }

   SayInvalidateText( hWnd );
}

/*  Changes color to a random selection, if color is available.
*  Forces a color change - if the random selection is the same
*  as the old one, it tries again.
*/

VOID SayChangeColor( hWnd )
   HWND        hWnd;
{
   HPS         hPS;
   LONG        lWindow;
   LONG        lNew;

   hPS = WinGetPS( hWnd );

   if( lColorMax <= 2 ) {
       lColor = GpiQueryColorIndex(
           hPS,
           WinQuerySysColor( hAB, SCLR_WINDOWTEXT ),
           0L
       );

   } else {
       lWindow = GpiQueryColorIndex(
           hPS,
           WinQuerySysColor( hAB, SCLR_WINDOW ),
           0L
       );
       do {
           lNew = rand() % lColorMax;
       } while( lNew = = lWindow || lNew = = lColor );
       lColor = lNew;
   }

   WinReleasePS( hPS );

}

/*  Handles scroll bar messages from the control dialog box.
*  Adjusts scroll bar position, taking its limits into account,
*  copies the scroll bar value into the adjacent edit control,
*  then sets the nDistance or nInterval variable appropriately.
*/

VOID SayDoBarMsg( hWndDlg, idBar, wCode, nThumb )
   HWND        hWndDlg;
   USHORT      idBar;
   USHORT      wCode;
   SHORT       nThumb;
{
   SHORT       nPos;
   SHORT       nOldPos;
   ULONG       lRange;

   nOldPos = nPos =
       (SHORT)WinSendDlgItemMsg(
           hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
       );

   lRange =
       WinSendDlgItemMsg(
           hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
       );

   switch( wCode ) {

       case SB_LINEUP:         -nPos;                    break;

       case SB_LINEDOWN:       ++nPos;                   break;

       case SB_PAGEUP:         nPos -= 10;               break;

       case SB_PAGEDOWN:       nPos += 10;               break;

       case SB_THUMBPOSITION:
       case SB_THUMBTRACK:     nPos = nThumb;            break;

       case SB_TOP:            nPos = LOUSHORT(lRange);  break;

       case SB_BOTTOM:         nPos = HIUSHORT(lRange);  break;

   }

   if( nPos < LOUSHORT(lRange) )
       nPos = LOUSHORT(lRange);

   if( nPos > HIUSHORT(lRange) )
       nPos = HIUSHORT(lRange);

   if( nPos = = nOldPos )
       return;

   WinSendDlgItemMsg(
       hWndDlg, idBar, SBM_SETPOS, (LONG)nPos, 0L
   );

   WinSetDlgItemShort( hWndDlg, idBar+1, nPos, FALSE );

   switch( idBar )
   {
     case ITEM_DISTBAR:
       nDistance = nPos;
       break;

     case ITEM_INTBAR:
       WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
       nInterval = nPos;
       WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );

       WinInvalidateRect( hWndWhat, NULL );
       break;
   }
}

/*  Terminates the application, freeing up allocated resources.
*  Note that this function does NOT return to the caller, but
*  exits the program.
*/

VOID SayExitApp( nRet )
   INT         nRet;
{
   if( hWndWhatFrame )
       WinDestroyWindow( hWndWhatFrame );

   if( hMsgQ )
       WinDestroyMsgQueue( hMsgQ );

   if( hAB )

       WinTerminate( hAB );

   exit( nRet );
}

/*  Fills a specified rectangle with the background color.
*  Checks that the rectangle is non-empty first.
*/

VOID SayFillRect( hPS, xLeft, xBottom, xRight, xTop )
   HPS         hPS;
   SHORT       xLeft;
   SHORT       xBottom;
   SHORT       xRight;
   SHORT       xTop;

{
   RECT        rcFill;

   if( xLeft >= xRight  ||  xBottom >= xTop )

       return;

   WinSetRect( hAB, &rcFill, xLeft, xBottom, xRight, xTop );

   WinFillRect(
       hPS,
       &rcFill,
       WinQuerySysColor( hAB, SCLR_WINDOW )
   );
}

/*  Initializes the application.  */

BOOL SayInitApp()
{
   HPS         hPS;
   BOOL        bOK;

   hAB = WinInitialize();
   if( ! hAB )

       return FALSE;

   hMsgQ = WinCreateMsgQueue( hAB, 0 );
   if( ! hMsgQ )
       return FALSE;

   WinLoadString(
       hAB, NULL, STR_NAME,  szAppName, sizeof(szAppName)
   );
   WinLoadString(
       hAB, NULL, STR_TITLE, szTitle,   sizeof(szTitle)
   );
   WinLoadString(
       hAB, NULL, STR_WHAT,  szText,    sizeof(szText)
   );

   bOK = WinRegisterClass(
       hAB,
       szAppName,
       (LPFNWP)SayWhatWndProc,
       CS_SYNCPAINT,
       0,

       NULL
   );
   if( ! bOK )
       return FALSE;

   hWndWhatFrame = WinCreateStdWindow(
       (HWND)NULL,
       FS_TITLEBAR | FS_SYSMENU |
           FS_MENU | FS_MINMAX | FS_SIZEBORDER,
       szAppName,
       szTitle,
       0L,
       (HMODULE)NULL,
       MENU_WHAT,
       &hWndWhat
   );

   if( ! hWndWhatFrame )
       return FALSE;

   WinShowWindow( hWndWhat, TRUE );

   return TRUE;
}

/*  Initializes one scroll bar in the control dialog.  */

VOID SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
   HWND        hWndDlg;
   SHORT       idBar;
   SHORT       nValue;
   SHORT       nMin;

   SHORT       nMax;
{
   HWND        hWndBar;

   hWndBar = WinWindowFromID( hWndDlg, idBar );

   WinSendDlgItemMsg(
       hWndDlg,
       idBar,
       SBM_SETSCROLLBAR,

       (LONG)nValue,
       MAKELONG( nMin, nMax )
   );

   WinSetDlgItemShort( hWndDlg, idBar+1, nValue, FALSE );
}

/*  Invalidates the text within the main window, adjusting the
*  text rectangle if it's gone out of bounds.
*/

VOID SayInvalidateText( hWnd )
   HWND        hWnd;
{
   SayLimitTextPos( hWnd );
   WinInvalidateRect( hWnd, &rcText );
}

/*  Checks the text position against the window client area
*  rectangle. If it's moved off the window in any direction,
*  forces it back inside, and also reverses the ptAdvance value
*  for that direction so it will 'bounce' off the edge. Handles
*  both the iconic and open window cases.
*/

VOID SayLimitTextPos( hWnd )
   HWND        hWnd;
{
   RECT        rcClient;
   POINT       ptTextSize;

   ptTextSize = ptCharSize;

   if( ! bIconic ) {
       npszText = szText;
       ptTextSize.x *= strlen(szText);
   }

   WinQueryWindowRect( hWndWhat, &rcClient );

   if( rcText.xLeft > rcClient.xRight - ptTextSize.x ) {
       rcText.xLeft = rcClient.xRight - ptTextSize.x;
       ptAdvance.x = -ptAdvance.x;
   }

   if( rcText.xLeft < rcClient.xLeft ) {
       rcText.xLeft = rcClient.xLeft;
       ptAdvance.x = -ptAdvance.x;
   }

   if( rcText.yBottom < rcClient.yBottom ) {
       rcText.yBottom = rcClient.yBottom;
       ptAdvance.y = -ptAdvance.y;
   }

   if( rcText.yBottom > rcClient.yTop - ptTextSize.y ) {
       rcText.yBottom = rcClient.yTop - ptTextSize.y;
       ptAdvance.y = -ptAdvance.y;
   }

   rcText.xRight = rcText.xLeft   + ptTextSize.x;
   rcText.yTop   = rcText.yBottom + ptTextSize.y;

}

/*  Moves the text within the window, by invalidating the old
*  position, adjusting rcText according to ptMove, and then
*  invalidating the new position.
*/

VOID SayMoveText( hWnd, ptMove )
   HWND        hWnd;
   POINT       ptMove;
{
   SayInvalidateText( hWnd );
   rcText.xLeft   =
       ptMove.x - (rcText.xRight - rcText.xLeft    >> 1);
   rcText.yBottom =
       ptMove.y - (rcText.yTop   - rcText.yBottom  >> 1);
   SayInvalidateText( hWnd );
}

/*  Sets one of the dialog scroll bars to *pnValue.  If that value
*  is out of range, limits it to the proper range and forces
*  *pnValue to be within the range as well.
*/

VOID SaySetBar( hWndDlg, pnValue, idBar )
   HWND        hWndDlg;
   SHORT *     pnValue;
   SHORT       idBar;
{
   ULONG       lRange;
   SHORT       nValue;
   BOOL        bOK;

   lRange =
       WinSendDlgItemMsg(
           hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
       );

   nValue =
       WinQueryDlgItemShort( hWndDlg, idBar+1, &bOK, FALSE );

   if(
       bOK  &&
       nValue >= LOUSHORT(lRange)  &&
       nValue <= HIUSHORT(lRange)
   ) {
       *pnValue = nValue;
       WinSendDlgItemMsg(
           hWndDlg, idBar, SBM_SETPOS, (LONG)nValue, (LONG)TRUE
       );
   } else {
       WinSetDlgItemShort(
           hWndDlg,
           idBar + 1,
           (INT)WinSendDlgItemMsg(
               hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
           ),
           FALSE
       );
   }
}

/*  Dialog function for the control panel dialog box.  */

ULONG FAR PASCAL SayWhatDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
   HWND        hWndDlg;
   USHORT      wMsg;
   ULONG       lParam1;
   ULONG       lParam2;
{
   HWND        hWndBar;
   RECT        rcWin;
   SHORT       n;

   switch( wMsg )
   {
   case WM_COMMAND:
       switch( LOUSHORT(lParam1) )
       {
         case IDOK:
           WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
           WinQueryWindowText(

               WinWindowFromID( hWndDlg, ITEM_WHAT ),
               sizeof(szText),
               szText
           );
           if( strlen(szText) = = 0 )
               WinLoadString(
                   hAB, NULL, STR_WHAT, szText, sizeof(szText)
               );
           npszText = szText;
           SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
           SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
           WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );
           WinInvalidateRect( hWndWhat, NULL );
           break;

         case IDCANCEL:
           WinDestroyWindow( hWndDlg );
           break;

         case ITEM_CLEAN:
         case ITEM_FLICKER:
           bCleanPaint = (BOOL)WinSendDlgItemMsg(

               hWndDlg, ITEM_CLEAN,
               BM_QUERYCHECK, 0L, 0L
           );
           break;
       }
       break;

   case WM_DESTROY:
       hWndPanel = NULL;
       break;

   case WM_HSCROLL:
       SayDoBarMsg(
           hWndDlg, LOUSHORT(lParam1),
           HIUSHORT(lParam2), LOUSHORT(lParam2)
       );
       break;

   case WM_INITDLG:
       WinQueryWindowRect( hWndDlg, &rcWin );
       WinMapWindowPoints(
           hWndWhat, (HWND)NULL, (LPPOINT)&rcWin.xLeft, 2

       );
       n = rcWin.xRight - ptScreenSize.x + ptCharSize.x;
       if( n > 0 )
           rcWin.xLeft -= n;
       rcWin.xLeft &= ~7;  /* byte align */
       n = rcWin.yTop - ptScreenSize.y + ptCharSize.y;
       if( n > 0 )
           rcWin.yBottom -= n;
       WinSetWindowPos(
           hWndDlg,
           (HWND)NULL,
           rcWin.xLeft,
           rcWin.yBottom,
           0, 0,
           SWP_MOVE
       );
       WinSetWindowText(
           WinWindowFromID( hWndDlg, ITEM_WHAT ), szText
       );
       WinSendDlgItemMsg(
           hWndDlg, ITEM_WHAT, EM_SETTEXTLIMIT,
           (LONG)sizeof(szText)-1, 0L
       );
       SayInitBar(
           hWndDlg, ITEM_INTBAR,  nInterval,
           MIN_INTERVAL, MAX_INTERVAL
       );
       SayInitBar(
           hWndDlg, ITEM_DISTBAR, nDistance,
           MIN_DISTANCE, MAX_DISTANCE
       );
       WinSendDlgItemMsg(
           hWndDlg, ITEM_CLEAN, BM_SETCHECK, (LONG)TRUE, 0L
       );
       break;
   }
   return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
}

/*  Painting procedure for the main window. Handles both the
*  clean and flickery painting methods for demonstration
*  purposes.
*/

VOID SayWhatPaint( hWnd )
   HWND        hWnd;
{
   HPS         hPS;
   RECT        rcPaint;
   GPOINT      gptText;

   hPS = WinBeginPaint( hWnd, (HPS)NULL, &rcPaint );

   GpiSetColor( hPS, lColor );

   SayLimitTextPos( hWnd );

   gptText.x = (LONG)rcText.xLeft;
   gptText.y = (LONG)rcText.yBottom;

   if( bCleanPaint ) {

       /* Clean painting, avoid redundant erasing */

       GpiSetBackMix( hPS, MIX_OVERPAINT );

       GpiCharStringAt(
           hPS,
           &gptText,
           (LONG)( bIconic ? 1 : strlen(szText) ),
           npszText
       );

       SayFillRect(
           hPS,
           rcPaint.xLeft,
           rcPaint.yBottom,
           rcText.xLeft,
           rcPaint.yTop
       );

       SayFillRect(
           hPS,
           rcText.xLeft,
           rcText.yTop,
           rcText.xRight,
           rcPaint.yTop
       );

       SayFillRect(
           hPS,
           rcText.xLeft,
           rcPaint.yBottom,
           rcText.xRight,
           rcText.yBottom
       );

       SayFillRect(
           hPS,
           rcText.xRight,
           rcPaint.yBottom,
           rcPaint.yRight,
           rcPaint.yTop
       );

    } else {

       /* Flickery painting, erase background and paint traditionally */

       WinFillRect(
           hPS,
           &rcPaint,
           WinQuerySysColor( hAB, SCLR_WINDOW )
       );

       GpiCharStringAt(
           hPS,
           &gptText,
           (LONG)( bIconic ? 1 : strlen(szText) ),
           npszText
       );
    }

    WinEndPaint( hPS );

    if( ! nInterval )
        SayInvalidateText( hWnd );
}

/*  Window function for the main window.  */

ULONG FAR PASCAL SayWhatWndProc( hWnd, wMsg, lParam1, lParam2 )
   HWND        hWnd;
   USHORT      wMsg;
   ULONG       lParam1;
   ULONG       lParam2;
{
   POINT       ptMouse;
   GSIZE       gsChar;
   HPS         hPS;
   LONG        ColorData[COLORDATAMAX];
   BOOL        bNowIconic;

   switch( wMsg )
   {
     case WM_BUTTON1DOWN:
       if( bMouseDown )
           break;
       WinStopTimer( hAB, hWnd, TIMER_MOVE );

       bMouseDown = TRUE;
       WinSetCapture( hAB, hWnd );
       ptMouse.x = LOUSHORT(lParam1);
       ptMouse.y = HIUSHORT(lParam1);
       SayMoveText( hWnd, ptMouse );
       return 0L;

     case WM_BUTTON1UP:
       if( ! bMouseDown )
           break;
       bMouseDown = FALSE;
       WinSetCapture( hAB, (HWND)NULL );
       ptMouse.x = LOUSHORT(lParam1);
       ptMouse.y = HIUSHORT(lParam1);
       SayMoveText( hWnd, ptMouse );
       WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
       return 0L;

     case WM_CHAR:
       if(
           ( LOUSHORT(lParam1) & KC_KEYUP )  ||
           ! ( LOUSHORT(lParam1) & KC_VIRTUALKEY )

       ) {
           break;
       }
       SayInvalidateText( hWnd );
       switch( HIUSHORT(lParam2) )
       {
         case VK_LEFT:
           rcText.xLeft -= ptCharSize.x;
           ptAdvance.x   = -1;
           ptAdvance.y   = 0;
           break;
         case VK_RIGHT:
           rcText.xLeft += ptCharSize.x;
           ptAdvance.x   = 1;
           ptAdvance.y   = 0;
           break;
         case VK_UP:
           rcText.yBottom -= ptCharSize.y >> 1;
           ptAdvance.x     = 0;
           ptAdvance.y     = -1;
           break;
         case VK_DOWN:

           rcText.yBottom += ptCharSize.y >> 1;
           ptAdvance.x     = 0;
           ptAdvance.y     = 1;
           break;
         default:
           return 0L;
       }
       SayInvalidateText( hWnd );
       nDistLeft = nDistance;
       return 0L;

     case WM_COMMAND:
       switch( LOUSHORT(lParam1) )
       {
         case CMD_ABOUT:
           WinDlgBox(
               (HWND)NULL, hWnd, (LPFNWP)SayAboutDlgProc,
               NULL, DLG_ABOUT, NULL
           );
           return 0L;

         case CMD_EXIT:

           WinDestroyWindow( hWndWhatFrame );
           return 0L;

         case CMD_WHAT:
           if( hWndPanel ) {
               WinSetWindowPos(
                   hWndPanel,
                   HWND_TOP,
                   0, 0, 0, 0,
                   SWP_ZORDER | SWP_ACTIVATE
               );
           } else {
               hWndPanel = WinLoadDlg(
                   (HWND)NULL,
                   (HWND)NULL,
                   (LPFNWP)SayWhatDlgProc,
                   NULL,
                   DLG_WHAT,
                   NULL
               );
           }
       }

       return 0L;

     case WM_CREATE:
       /* find out character/screen sizes, number of colors */
       hPS = WinGetPS( hWnd );
       GpiQueryCharBox( hPS, &gsChar );
       GpiQueryColorData( hPS, (LONG)COLORDATAMAX, ColorData );
       WinReleasePS( hPS );
       lColorMax = ColorData[3];
       ptCharSize.x = gsChar.width;
       ptCharSize.y = gsChar.height;
       ptScreenSize.x =
         WinQuerySysValue( (HWND)NULL, SV_CXSCREEN );
       ptScreenSize.y =
         WinQuerySysValue( (HWND)NULL, SV_CYSCREEN );
       /* initialize timer */
       srand( (INT)time(NULL) );
       WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
       return 0L;

     case WM_DESTROY:
       if( hWndPanel )

           WinDestroyWindow( hWndPanel );
       WinPostQueueMsg( hMsgQ, WM_QUIT, 0L, 0L );
       return 0L;

     case WM_ERASEBACKGROUND:
       return 1L;  /* don't erase */

     case WM_MINMAX:
       bNowIconic = ( LOUSHORT(lParam1) = = SWP_MINIMIZE );
       if( bIconic != bNowIconic ) {
           bIconic = bNowIconic;
           if( bIconic )
               WinStopTimer( hAB, hWnd, TIMER_CHAR );
           else
               WinStartTimer( hAB, hWnd, TIMER_CHAR, 1000 );
       }
       return 1L;

     case WM_MOUSEMOVE:
       if( bMouseDown ) {
           ptMouse.x = LOUSHORT(lParam1);
           ptMouse.y = HIUSHORT(lParam1);

           SayMoveText( hWnd, ptMouse );
       }
       return 0L;

     case WM_PAINT:
       SayWhatPaint( hWnd );
       return 0L;

     case WM_SIZE:
       SayInvalidateText( hWnd );
       nDistLeft = 0;
       SayAdvanceTextPos( hWnd );
       return 0L;

     case WM_TIMER:
       switch( LOUSHORT(lParam1) ) {
           case TIMER_MOVE:
               SayAdvanceTextPos( hWnd );
               break;
           case TIMER_CHAR:
               SayAdvanceTextChar( hWnd );
               break;

       }
       return 0L;
   }
   return WinDefWindowProc( hWnd, wMsg, lParam1, lParam2 );
}

/*  Main function for the application.  */

void cdecl main( nArgs, pArgs )
   INT         nArgs;
   PSZ         pArgs;
{
   QMSG        qMsg;

   if( ! SayInitApp() )
       SayExitApp( 1 );

   while( WinGetMsg( hAB, &qMsg, (HWND)NULL, 0, 0 ) ) {

       if( hWndPanel  &&  WinProcessDlgMsg( hWndPanel, &qMsg ) )

           continue;

       WinDispatchMsg( hAB, &qMsg );
   }

   SayExitApp( 0 );
}

████████████████████████████████████████████████████████████████████████████

Programming Considerations in Porting to Microsoft XENIX System V/386

───────────────────────────────────────────────────────────────────────────
Also see the related article:
 Demand Paging and Virtual Memory
───────────────────────────────────────────────────────────────────────────

Martin Dunsmuir

XENIX(R) System V/386 is the Microsoft version of the UNIX(R) Operating
System ported to the Intel(R) 386 microprocessor. The product, which was
released to OEMs in the summer of 1987, is the first from Microsoft to take
full advantage of the features of the Intel 386 microprocessor. In
particular, XENIX allows 32-bit applications to be written for the first
time.

Microsoft has been active in the UNIX business since its inception, and in
fact, XENIX predates even MS-DOS(R) as a Microsoft product. Since 1983, the
XENIX development effort has concentrated on the Intel microprocessors──the
286 introduced by Intel in 1983 and more recently the 386. By concentrating
on one instruction set and chip architecture, Microsoft has been able to
develop the world's largest installed base of binary-compatible UNIX
systems. Currently, XENIX accounts for about 70 percent of all UNIX
licenses sold, or roughly 250,000 units, and more than 1000 different
applications are available. Although these numbers may sound small when
compared with the numbers quoted for MS-DOS, the majority of these systems
are used in multiuser configurations supporting between 2 and 16 users.

Most of the current XENIX installed base is on 286-based PCs such as the
IBM(R) PC AT(R). XENIX is used primarily as a cost-effective solution for
small businesses that require multiuser access; it has sold particularly
well in vertical markets that lend themselves to a customized "systems
solution." Two such markets are dentistry and accounting.

There was great excitement when Intel informed Microsoft that it was
planning the 386 chip-and Microsoft set out to find a way to take advantage
of the chip's features within the XENIX environment. In particular,
Microsoft wanted to give developers the ability to create 32-bit
applications and provide support within the operating system for virtual
memory and demand paging──features that can greatly increase the throughput
of a computer system.

Another major design goal was to be sure that existing XENIX 286 users were
not prevented from moving up to the 386. Microsoft wanted its installed base
to be able to take advantage of the increased performance without having to
buy new versions of their applications. Since the 386 chip supports both 16-
and 32-bit segments in its architecture, Microsoft has been able to create
an environment in which both 16- and 32-bit programs can be executed
simultaneously. The features of demand paging and virtual memory are
available transparently to both 16- and 32-bit applications.

Implementing support for segments independently of the paging subsystem
provides full performance for old applications without slowing down the
execution of the new 32-bit programs (see Figure 1).

Support for 32-bit applications is the key to the continued success of
XENIX. It opens the door to the creation of much more powerful programs, as
well as making it easier for developers to move existing UNIX applications
onto XENIX. UNIX 32-bit programs being ported to XENIX 286 often needed
extensive rewriting to make them work well in a 16-bit environment. This
article outlines the considerations that will help programmers in choosing
between 16- and 32-bit program models when developing applications or
migrating to XENIX System V/386.


Small-Model Programs

In 16-bit mode, as used on XENIX 286, a program is composed of two segments
up to 64Kb in size. One segment contains program code; the other segment
contains data (the stack is of fixed size and resides in the data segment).
An example of a small-model program is shown in Figure 2.

The program in Figure 2 has only one data and one code segment, so it is
not necessary to change the contents of the segment registers while the
program is running. At load time, the operating system initializes them
to point to the memory in which the program executes (via the LDT). In
particular, the compiler or assembler programmer does not have to take
account of changing segment registers during the program execution.

Also of note is the fact that in the small model, both integers and pointers
to data objects are 16-bit quantities and therefore interchangeable. This is
important because many programs implicitly make this assumption.
Unfortunately, although many commands and utilities are less than 64Kb in
size, most third-party applications are larger than this; they require
multiple code and/or data segment support. Figure 3 shows a multisegment
program.


Large- and Middle-Model

Programs can overflow the 64Kb limit with their code or their data, or both
at the same time. Programs that exceed 64Kb of code but still have less than
64Kb of data are called middle-model programs. Large-model programs exceed
64Kb in both their code and data segments.


Large Code

The program is broken down by the linker into pieces that will fit into
64Kb. If a program's code exceeds 64Kb, then it is necessary to place
different parts of it in different segments. (Because the breaks occur on
function boundaries, each piece can be less than 64Kb in size.) The compiler
must also gener-ate code that automatically reloads the CS register when
the thread of execution moves from one segment to another. This results in a
program that is slightly slower than the equivalent small-model image. The
effect is not too drastic because within each subroutine CS remains constant
so the frequency of segment register reloads is relatively low in
comparison with the number of instructions executed.


Large Data

Data structures are spread among a number of different segments when data
exceeds 64Kb. Again, the linker fills each segment with data structures as
fully as possible. The performance penalty to be paid for going to large
data is much larger than in the case of code because the frequency of
intersegment data accesses is generally much greater than that of
intersegment branches. The C compiler does not know which segment a
particular data structure resides in at compile time; this causes a large
number of unnecessary intersegment calls to be generated. Another problem
in moving to large model is the fact that the size of data pointers
increases to 32 bits. This means that the size of an integer is no longer
equal to the size of a pointer, and programs that rely on this equality,
either implicitly or explicitly, break. This is one of the primary problems
developers experience when porting existing 32-bit UNIX programs to XENIX
286. A summary of the different models can be seen in Figure 4.


Hybrid Model

Using the 286, where 16-bit segments are the norm but most useful
applications exceed 64Kb in size, it is important for programmers to
understand how to design their programs to reduce the effect of the
multiple segment accesses. One way of doing this is to select specific data
structures to be placed in separate far segments, while keeping
indiscriminately accessed data structures (and the stack if possible) in a
single near segment. The code generated by the Microsoft(R) C compiler in
this case is much more efficient because the far keyword, used to mark
specific data structures, gives the compiler a hint as to when it should
reload segment registers.

Use of a hybrid model with carefully designed programs comes close to the
performance of small-model programs, even though they exceed 64Kb in size.
However, there is a down side to this approach──it makes programs inherently
less portable between XENIX 286 and other UNIX environments. Also,
converting an existing 32-bit UNIX application to a hybrid model is
complicated by the differences in pointer and integer size that make large-
model ports such a problem.


32-Bit Programming

In contrast to the complexity of a multisegment 286 program, the native 386
program structure is very simple (see Figure 5). Each program consists of
one code segment along with one data segment, and each segment can be very
large in size. (The exact limit depends on the availability of real
memory and swap space and is typically a few megabytes).

Because the address space is large, it is not necessary to support multiple
segments in 32-bit mode, either in the operating system or in the C
compiler. When a program is loaded, all the segment registers are
initialized to static values and remain unchanged while the program is
executing. In 32-bit mode, the stack lives in the data segment and grows
down to lower addresses, while the data segment extends upward to higher
addresses.

XENIX 386 programs are truly 32-bit; they support 32-bit integers and all
pointers are 32 bits in length. This eases the problems of porting existing
32-bit applications to XENIX 386 in 32-bit mode.

Other advantages offered by the 32-bit mode of the 386 are more orthogonal
registers and addressing modes, which allow better code generation and more
register variables, plus extra instructions that improve in-line code
generation. Thirty-two-bit programs generally exhibit a significant
performance advantage over the 16-bit versions.


New XENIX Applications

The introduction of XENIX 386 no longer constrains the developer to the 16-
bit architecture. If he chooses, he can develop his application in 32-bit
mode. However, the choice between a 16- and a 32-bit architecture for a
new application is not as simple as it appears at first glance. 32-bit
programs will only execute on XENIX 386, whereas 16-bit applications will
execute on both XENIX 286 and XENIX 386. The installed base of XENIX 386 is
still small, but it is almost certain to exceed that of XENIX 286 in time. A
16-bit application may be a better choice for developers who want to address
the largest possible installed base. Let's look at the trade-offs that must
be considered when making the choice between 16 and 32 bits.

The developer should ask himself the following questions:

 ■  What is the size of the application, both code and data?

 ■  Is the application an existing UNIX program being ported to XENIX?

 ■  Is the application an existing MS-DOS program or aimed at the MS-DOS,
    OS/2, and UNIX markets?

 ■  For new applications, what is the target market for the application?
    Is it limited to XENIX or does it have wider appeal in other UNIX or
    DOS markets?

 ■  What are the application's performance requirements?


Application Size

In many ways size is the most important consideration; unfortunately for
new applications it is most likely the hardest to answer. For a simple
application it is probably wise to build the application first as a 32-
bit program and then see if it will fit into 16 bits. At this point it
should be remembered that large data is a much more serious performance
limitation to 16-bit programs-programs with more than 16 bits of code but
less than 16 bits of data can be built as 16-bit middle-model programs
without serious performance degradation.

Another approach that can be used to fit a more complex program into the
64Kb address space is to break it down into a number of separate,
communicating processes, each of which fits into the smaller address
space. Not all programs are amenable to such an architecture. Breaking an
application into pieces can also limit portability into the MS-DOS
world.


Portability

Many developers of UNIX applications for UNIX systems other than XENIX 286
have programs that are designed implicitly for the 32-bit world. This is
because XENIX 286 is one of the few UNIX systems to run on a 16-bit
processor. Even if size is not a consideration, the work required to port
UNIX applications from 32 to 16 bits has often deterred developers from
doing the port.

Such developers are best advised to build their applications for XENIX
386 only. Debugging those problems summarized earlier in this article is
often too great an effort to warrant porting a program to XENIX 286. This
extra development effort can be considered for a later release if market
pressure is felt.

When porting existing MS-DOS applications to UNIX, it is usually more
feasible to build an application in 16 bits. This is certainly the best and
easiest option if the application contains a significant amount of
assembler code. Since the XENIX and MS-DOS macro assemblers accept the same
source syntax in 16-bit mode, assembly code that is not environment-specific
should port directly to XENIX.

Traditionally, UNIX and MS-DOS applications markets have been separated by a
wide gap in complexity. This is because the architecture of real-mode MS-
DOS programs is very different from UNIX. With the advent of OS/2, the
underlying support provided by the two operating systems is now comparable,
so it may make sense for new applications to be developed that can easily
be hosted in both XENIX and OS/2 environments. If this is the case, it makes
more sense to build the application for the 16-bit environment common to
both XENIX and OS/2 and to delay the development of a 32-bit application
until a 32-bit version of OS/2 becomes available.

Another consideration for simpler applications is the use of the C library
calls supported by the Microsoft C Compiler under MS-DOS. These calls, which
embody a subset of the UNIX C library calls, can make it relatively easy to
build a program that can be simply rehosted in both XENIX and MS-DOS
environments. A good example of this approach would be the Microsoft 286 C
compiler itself, which is recompiled and linked with different run-time
libraries for execution on MS-DOS or XENIX 286. The task of creating a
common source code for both MS-DOS and XENIX versions of the compiler is
greatly facilitated by the fact that the XENIX and MS-DOS linkers both
accept the same relocatable format as input (although they generate a
different executable file format).


XENIX and UNIX Markets

Building applications that port easily between XENIX 286 and other UNIX
platforms has generally been difficult. It is prudent──if a source portable
application is desired──to remain within the 32-bit world. The 32-bit XENIX
386 environment is completely compatible with the System V Interface
Definition (SVID), and thus there should be very little difficulty in moving
a carefully designed program from XENIX 386 to other UNIX platforms.


Performance

Although performance is a combination of many factors, it is most strongly
linked to the architecture of the program and to the inherent speed of the
host computer. All architectural considerations being equal, a 32-bit
program will execute faster than a 16-bit program on the same 386 CPU.
Applications that are being ported from the earlier 286 or 8086 worlds onto
the 386 will experience an increase in raw 16-bit performance, simply by
running the code on a 386, that more than offsets the need to consider
rehosting into 32-bit mode.

For new XENIX applications, especially those being ported from other 32-bit
processors, where a 16-bit port is a serious possibility, it is important to
understand the performance degradation seen on the 386 between 16-bit and
32-bit code. The operating system itself runs in 32-bit mode, and some part
of a program's execution time is spent in this code. The decrease in speed
when moving to 16 bits is not as great as a simple comparison of CPU-bound
16 and 32 performance might indicate. Figure 8 shows the relative
execution times of two small C programs, "Cpubound" and "IObound," built
as small-16, middle-16, large-16, and small-32 programs on XENIX 386.
Figures 6 and 7 show the source code of these programs .

An analysis of Figure 8 shows that the 32-bit architecture offers a
significant performance advantage for CPU-bound programs that do a mix of
arithmetic, pointer processing, and function calls. There is no
performance difference among the various 16- and 32-bit models chosen for
I/O-bound activities where the processing is all within the kernel.
Although the performance of a 16-bit application on XENIX 386 falls short
of the 32-bit performance, it is still between two and three times greater
than the performance when that program is run on an 8-Mhz 286. The
difference in performance between the 386 host and the 286 target must be
factored in when measuring 16-bit performance on XENIX 386.


Conclusions

When designing an application for the XENIX 386 environment, the developer
must weigh a number of conflicting criteria. The foremost problem is
whether to build the program in 16- or 32-bit mode. Further questions must
address the intended market as well as the performance and portability
required of the completed product. Lastly, it is important to consider
future compatibility requirements.

Microsoft and AT&T are currently working together to merge XENIX 386 and
AT&T's UNIX System V/386 Release 3.2 into a single UNIX system that will be
marketed jointly by the two companies. This Merged Product (MP) will
support all the existing 286 and 386 executable formats common to UNIX and
XENIX on the 386, thereby allowing all existing applications to run.

The emphasis for developers using the Merged Product will be to establish
the UNIX/386, 32-bit mode program interface as the standard for new
applications. This standard will be a superset of the current XENIX System
V/386 program interface, without the support for XENIX-specific system call
extensions. This means that in the long run there will be one binary
standard, developed and supported by Microsoft and AT&T, which will run on
all 386 machines running UNIX, thereby stabilizing the market.

Developers who would like their programs to be source compatible with the
new binary standard may want to avoid the use of XENIX system call
extensions before the Merged Product becomes available in mid-1988. This
applies particularly to the use of 32-bit applications (see Figure 9).
Although kernel support is provided for XENIX extensions in the MP, minimal
development tools will be provided. Debugging support will be limited to
the UNIX System V/386 Release 3.2 binary standard. Without exception, the
functionality of the XENIX call extensions is supported within the
framework of the UNIX program interface.


Figure 1:  Page table entries are maintained in groups of 16. This allows a
          286 segment to expand to 64Kb without existing table entries.

   ░░░░░░░░░░░░░░░░Mapping 286 Programs under XENIX 386░░░░░░░░░░░░░░░░░░

                                         Data Page Table
                                   ╔══════════════════════════╗
         Selector                  ║  Available for Expansion ║
           LDT                     ╟──────────────────────────╢
    ╔════════════════╗             ╟──────────────────────────╢
5FH ║     DS #2      ║             ║           32Kb           ║- 8 pages
    ╟────────────────╢             ╟──────────────────────────╢      mapped
57H ║     DS #1      ║────────────►║           64Kb           ║-16 pages
    ╟────────────────╢             ╚══════════════════════════╝      mapped
4FH ║     TS #3      ║                   Text Page Table
    ╟────────────────╢             ╔══════════════════════════╗
47H ║     TS #2      ║─────┐       ║          Unused          ║
    ╟────────────────╢     │       ╟──────────────────────────╢
3FH ║     TS #1      ║──┐  │       ║          Unused          ║
    ╚════════════════╝  │  │       ╟──────────────────────────╢
                        │  │       ║            8Kb           ║- 2 pages
                        │  │       ╟──────────────────────────╢      mapped
                        │  └──────►║            64Kb          ║-16 pages
                        │          ╟──────────────────────────╢      mapped
                        └─────────►║            64Kb          ║-16 pages
                                   ╚══════════════════════════╝      mapped


Figure 2:  A Small-Model 286 Program

     ┌────────────────────────────────────────────────────────┐
     │        Text Segment               Data Segment         │█
     │  ╔═════════════════════╗64Kb╔═════════════════════╗64Kb│█
     │  ║                     ║    ║                     ║    │█
     │  ║      Unused         ║    ║    Available heap   ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ╟─────────────────────╢    ╟─────────────────────╢    │█
     │  ║                     ║    ║                     ║    │█
     │  ║                     ║    ║      heap (BSS)     ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ║       Text          ║    ╟─────────────────────╢    │█
     │  ║    (fixed size)     ║    ║                     ║    │█
     │  ║                     ║    ║     Fixed stack     ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ║                     ║    ╟─────────────────────╢    │█
     │  ║                     ║    ║                     ║    │█
     │  ║                     ║    ║  Initialized data   ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ║                     ║    ║                     ║    │█
     │  ╚═════════════════════╝0   ╚═════════════════════╝0   │█
     │       Selector 3FH                Selector 47H         │█
     └────────────────────────────────────────────────────────┘█
       █████████████████████████████████████████████████████████


Figure 3:  Large-Model 286 Program Layout

         ╔════════════════╗            ╔════════════════╗
         ║    Unused      ║            ║                ║
         ╟────────────────╢            ║   1st segment  ║
         ║     TS #3      ║            ║  available for ║
         ║      8Kb       ║            ║      heap      ║
      4FH╚════════════════╝         67H╚════════════════╝

         ╔════════════════╗            ╔════════════════╗
         ║                ║            ║                ║
         ║     TS #2      ║            ║     DS #2      ║
         ║      64Kb      ║            ║    far data    ║
         ║                ║            ║                ║
      47H╚════════════════╝         5FH╚════════════════╝

         ╔════════════════╗            ╔════════════════╗
         ║                ║            ║                ║
         ║     TS #1      ║            ║      DS #1     ║
         ║      64Kb      ║            ║  stack & data  ║
         ║                ║            ║                ║
      3FH╚════════════════╝         57H╚════════════════╝


Figure 4:  Camparison of 286 Program Models

Name of
Model      Max. Text  Max. Data  Stack          Heap             Performance

Small      <=64Kb     <=64Kb     Fixed (<64Kb)  In Data (<64Kb)  Best

Middle     >64Kb      <=64Kb     Fixed          In Data (<64Kb)  Good

Compact    <=64Kb     >64Kb      <=64Kb         >64Kb            Poor

Large      >64Kb      >64Kb      <=64Kb         >64Kb            Poorest

Hybrid
Data       <=64Kb     >64Kb      Fixed (<64Kb)  <64Kb            Good


Figure 5:  Program Layout in 386 Mode

                   ┌─────────────────────────────────────────────────────┐
                   │    Text Segment            Data Segment             │█
         The stack │ ╔═════════════════╗4Gb ╔═════════════════╗4Gb       │█
        grows down │ ║                 ║    ║     Video RAM   ║virtual   │█
     to 0 virtual, │ ║                 ║    ╟─────────────────╢          │█
    while the heap │ ║                 ║    ║      Unused     ║          │█
     grows up. The │ ║                 ║    ╟─────────────────╢          │█
        sum of the │ ║                 ║    ║   Shared Data   ║          │█
      mapped text, │ ║                 ║    ╟─────────────────║6400000H  │█
         data, and │ ║                 ║    ║    Available    ║          │█
      stack cannot │ ║     Unused      ║    ║    for heap     ║          │█
        exceed the │ ║                 ║    ║    expansion    ║          │█
      installation │ ║                 ║    ╟─────────────────║          │█
   dependent limit │ ║                 ║    ║      Heap       ║          │█
    (typically the │ ║                 ║    ╟─────────────────╢          │█
  sum of installed │ ║                 ║    ║ Initialized Data║          │█
      RAM plus the │ ║                 ║    ╟─────────────────╢          │█
           size of │ ╟─────────────────╢    ║     Stack       ║          │█
        the paging │ ║                 ║    ╟─────────────────╢1880000H  │█
           area on │ ║      Text       ║    ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║          │█
        the disk). │ ║                 ║    ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║          │█
                   │ ╚═════════════════╝0   ╚═════════════════╝0         │█
                   └─────────────────────────────────────────────────────┘█
                     ██████████████████████████████████████████████████████


Figure 7:  IObound.c

/*
* IObound.c
*/

#define IMAX 100
#define JMAX 100

#define BFS 2
char buffer[BFS];

main()
{
int i, j;
int fd;

/* Create a Disk File */
fd = creat("scratch", 0600);

for(i=0; i<IMAX; i++){
   sync();
   for(j=0; j<JMAX; j++){
      write(fd, buffer, BFS);
   }
}


sync();
/* Return to Beginning of the File */
lseek(fd, 0, 0);

for(i=0; i<IMAX; i++){
   sync();
   for(j=0; j<JMAX; j++){
      read(fd, buffer, BFS);
   }
}

/* Remove the File */
unlink('scratch');
exit(0);
}


Figure 6:  Cpubound.c

/*
*      Cpubound.c
*/
#define IMAX 100
#define JMAX 1000

int id[IMAX];
int jd[JMAX];

main()
{
int i, j;

for(i=0; i<IMAX; i++){
   id[i] = i;
   for(j=0; j<JMAX; j++){
      jd[i] = j; calli(id, jd, j);
   }
}
exit(0);
}
calli(i, j, c)
int *i, *j;
int c;
{

 int t;
 int ti = i[c];
 int tj = j[c];

 while(ti- -)
    t += (*(i++))+(*(j++))+(tj-);

 return(t);
}


Figure 8:  Performance Table

CPU Bound Performance (normalized)

                   Real time        User time     System time

Small-Model  286    32.4 (0.59)      32.2          0.0
Middle-Model 286    40.6 (0.47)      40.5          0.0
Large-Model  286    57.5 (0.33)      57.4          0.0
Small-Model  386    19.0 (1.00)      18.9          0.0

I/O Bound Performace (normalized)

                   Real time        User time     System time

Small-Model  286    38.5 (1.00)      0.3           13.3
Middle-Model 286    38.6 (1.00)      0.7           12.8
Large-Model  286    42.4 (0.91)      0.4           14.8
Small-Model  386    38.4 (1.00)      0.2           12.4


Figure 9:  XENIX System Call Extensions to be avoided for portability.

╓┌──────────────┌────────────────────────────────────────────────────────────╖
Entry Point    Function

chsize         adjust file size

creatsem       semaphore operations
nbwaitsem      semaphore operations
Entry Point    Function
nbwaitsem      semaphore operations
opensem        semaphore operations
sigsem         semaphore operations
waitsem        semaphore operations

execseg        execute data
unexecseg      execute data

ftime          obsolete UNIX time function

locking        XENIX file locking

nap            sleep for a short time

proctl         process specific control function

rdchk          check for input without reading

sdenter        XENIX shared data extension
sdfree         XENIX shared data extension
Entry Point    Function
sdfree         XENIX shared data extension
sdget          XENIX shared data extension
sdgetv         XENIX shared data extension
sdleave        XENIX shared data extension
sdwaitv        XENIX shared data extension

shutdn         shutdown system

swapon         control paging devices


───────────────────────────────────────────────────────────────────────────
Demand Paging and Virtual Memory
───────────────────────────────────────────────────────────────────────────

Demand paging is a feature of the XENIX 386 operating system, built on top