home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / msj / msj3.txt < prev    next >
Encoding:
Text File  |  2013-11-08  |  1.8 MB  |  42,813 lines

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  Microsoft Systems Journal Volume 3
  2.  
  3.  ────────────────────────────────────────────────────────────────────────────
  4.  
  5.  Vol. 3 No. 1 Table of Contents
  6.  
  7.  ────────────────────────────────────────────────────────────────────────────
  8.  
  9.  Preparing for Presentation Manager: Paradox(R) Steps Up to Windows 2.0
  10.  
  11.  Ansa Software is developing versions of the Paradox data base product for
  12.  the 80386, OS/2, UNIX(R)/XENIX(R), and Microsoft(R) Windows environments,
  13.  all largely sharing the same code. This article explains how Ansa ported
  14.  their large MS-DOS(R) application to the Windows environment for one of
  15.  the new versions.
  16.  
  17.  
  18.  Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager
  19.  
  20.  Microsoft OS/2 Presentation Manager offers the best of the Microsoft Windows
  21.  operating environment and the protected-mode OS/2 operating system. The
  22.  sample program SayWhat highlights the similarities and differences between
  23.  Presentation Manager and Windows programming techniques.
  24.  
  25.  
  26.  Programming Considerations In Porting To Microsoft(R) XENIX(R) System
  27.  V/386
  28.  
  29.  XENIX System V/386, Microsoft's version of UNIX for the 386, supports
  30.  virtual memory, demand paging, and both 286 and 386 mode execution. This
  31.  article outlines considerations that help programmers choose between 16- and
  32.  32-bit program models when developing applications for XENIX V/386.
  33.  
  34.  
  35.  HEXCALC: An Instructive Pop-Up Calculator For Microsoft(R) Windows
  36.  
  37.  HEXCALC, a pop-up window hexadecimal calculator, explores a method of using
  38.  dialog box templates to define the layout of child window controls on a
  39.  program's main window, a technique illustrating features of both Microsoft
  40.  Windows Version 2.0 and the OS/2 Presentation Manager.
  41.  
  42.  
  43.  Effectively Using Far and Huge Data Pointers In Your Microsoft(R)
  44.  C Programs
  45.  
  46.  Programs that use "huge" pointers to access very large amounts of data are
  47.  often affected by the expense of using relatively slow 32-bit arithmetic.
  48.  Under certain conditions, huge pointers can be converted to far pointers,
  49.  resulting in a significant decrease in access time.
  50.  
  51.  EMS Support Improves Microsoft(R) Windows 2.0 Application Performance
  52.  
  53.  Windows 2.0 uses expanded memory to bank-switch Windows applications,
  54.  allowing multiple large programs to run simultaneously as if each were
  55.  the only application running.The article examines how Windows' exploitation
  56.  of LIM 4.0 expanded memory support can affect your Windows application.
  57.  
  58.  
  59.  EDITOR'S NOTE
  60.  
  61.  There has been much speculation about the amount of effort required to move
  62.  applications from the Windows 2.0 environment to the OS/2 Presentation
  63.  Manager. With varying estimates and opinions ringing in our ears, we decided
  64.  to devote a major part of this issue to exploring what's involved in
  65.  programming for the Presentation Manager.
  66.  
  67.  We turned to Michael Geary, who spent a great deal of the last year
  68.  exploring Presentation Manager. He provides us with a step-by-step guide to
  69.  moving a Windows 2.0 application to Presentation Manager. To illustrate the
  70.  differences, we have includeded side-by-side the complete code listings of
  71.  both versions of his tutorial program.
  72.  
  73.  Charles Petzold, inveterate Windows expert, also provides insight on the
  74.  topic with parallel Microsoft(R) Windows 2.0 and Presentation Manager
  75.  versions of his instructive HEXCALC program.
  76.  
  77.  Ansa Software has had notable success with the MS-DOS(R) version of its
  78.  Paradox(R) database. Currently under development are Paradox versions for
  79.  Microsoft Windows, OS/2 Presentation Manager, UNIX(R) 5.3 and XENIX(R),
  80.  as well as protected-mode 80386. We talked with Richard Schwartz, vice
  81.  president of software development and cofounder of Ansa, to discover how one
  82.  software firm is preparing for their move to Presentation Manager.
  83.  
  84.  With all the hoopla surrounding the OS/2 systems, we don't want to forget
  85.  the importance of recent XENIX and MS-DOS developments. In particular, the
  86.  new version of XENIX for the 386 environments has generated much interest,
  87.  including questions on the portability between different versions of XENIX.
  88.  Martin Dunsmuir, who is responsible for XENIX development at Microsoft,
  89.  answers these questions and others.
  90.  
  91.  We look forward to 1988, and the new developments this coming year will
  92.  bring. Look for MSJ to be there with the details.──ED.
  93.  
  94.  
  95.  Masthead
  96.  
  97.  JONATHAN D. LAZARUS
  98.  Editor and Publisher
  99.  
  100.  EDITORIAL
  101.  
  102.  TONY RIZZO
  103.  Technical Editor
  104.  
  105.  CHRISTINA G.DYAR
  106.  Associate Editor
  107.  
  108.  JOANNE STEINHART
  109.  Production Editor
  110.  
  111.  GERALD CARNEY
  112.  Staff Editor
  113.  
  114.  KIM HOROWITZ
  115.  Editorial Assistant
  116.  
  117.  ART
  118.  
  119.  MICHAEL LONGACRE
  120.  Art Director
  121.  
  122.  VALERIE MYERS
  123.  Associate Art Director
  124.  
  125.  CIRCULATION
  126.  
  127.  WILLIAM B. GRANBERG
  128.  Circulation Manager
  129.  
  130.  L. PERRIN TOMICH
  131.  Assistant to the Publisher
  132.  
  133.  DONNA PUIZINA
  134.  Administrative Assistant
  135.  
  136.  Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
  137.  in part or in whole without permission is prohibited.
  138.  
  139.  Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
  140.  NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
  141.  Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
  142.  President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
  143.  William Neukom, Secretary.
  144.  
  145.  Microsoft Corporation assumes no liability for any damages resulting from
  146.  the use of the information contained herein.
  147.  
  148.  Microsoft, MS-DOS, XENIX, CodeView and the Microsoft logo are registered
  149.  trademarks of Microsoft Corporation. PageMaker is a registered trademark of
  150.  Aldus Corporation. Paradox is a registered trademark of Ansa Software, a
  151.  Borland Company. Apple and Macintosh are registered trademarks of Apple
  152.  Computer, Inc. UNIX is a registered trademark of American Telephone and
  153.  Telegraph Company. RAMpage! is a registered trademark of AST Research, Inc.
  154.  Advantage! is a trademark of AST Research, Inc. IBM and PC/AT are registered
  155.  trademarks of International Business Machines Corporation. PS/2 and Micro
  156.  Channel are trademarks of International Business Machines Corporation. Intel
  157.  is a registered trademark of Intel Corporation. Above is a trademark of
  158.  Intel Corporation. Lotus and 1-2-3 are registered trademarks of Lotus
  159.  Development Company. Microrim and R:BASE are registered trademarks of
  160.  Microrim, Inc.
  161.  
  162.  ████████████████████████████████████████████████████████████████████████████
  163.  
  164.  Preparing for Presentation Manager: Paradox Steps Up to Windows 2.0
  165.  
  166.  ───────────────────────────────────────────────────────────────────────────
  167.  Also see the related article:
  168.    Paradox Under Microsoft(R) OS/2
  169.  ───────────────────────────────────────────────────────────────────────────
  170.  
  171.  Craig Stinson
  172.  
  173.  Ansa Software, the Belmont, Calif., firm recently acquired by Borland
  174.  International, sells only one product, but that product, the relational
  175.  database manager Paradox(R), will appear in four new versions during the
  176.  coming year. A fifth new version will probably be available in early 1989.
  177.  
  178.  The offspring are aimed at different operating environments:
  179.  Microsoft(R) Windows Version 2.0, OS/2 systems Version 1.0, the OS/2
  180.  Presentation Manager, UNIX(R) 5.3 and XENIX(R), and the 80386 running MS-
  181.  DOS(R) 3.x in protected mode with the help of a 386 MS-DOS compatibility
  182.  tool. Paradox 386 was shown at last November's Comdex and will probably be
  183.  available by the time you read this article; it won't require a
  184.  compatibility tool, just a 386 machine. The OS/2 1.0 version should be
  185.  released during the first quarter of 1988, the Windows version in the
  186.  second quarter, the UNIX/XENIX version in the third quarter, and the
  187.  Presentation Manager version in about a year, depending on the date of the
  188.  OS/2 Presentation Manager's arrival.
  189.  
  190.  All Paradox versions, old and new, will have multiuser capability and will
  191.  be data-compatible. With versions for all the major personal computing
  192.  environments (except Macintosh(R)──Ansa is currently evaluating the
  193.  feasibility of a Mac version), Ansa will be in a position to serve the vast
  194.  majority of desktop users. And all users at various nodes of a network will
  195.  be able to access and edit a common database, regardless of the hardware or
  196.  operating systems they're running.
  197.  
  198.  
  199.  Sound Design Pays Off
  200.  
  201.  What type of effort is involved in porting a large, complex MS-DOS
  202.  application such as Paradox to all these different operating environments?
  203.  In a recent interview with MSJ, Richard Schwartz, vice president for
  204.  software development and a cofounder of Ansa, said that the move from one
  205.  character-based system to another has been relatively straightforward,
  206.  thanks largely to the foresight of the original designers of Paradox.
  207.  
  208.  "We designed Paradox from the beginning to be very clean and portable," says
  209.  Schwartz. "We tried not to play dirty tricks getting at the operating system
  210.  or creating hardware dependencies." This is not to say, of course, that the
  211.  original Paradox doesn't write directly to the screen. "We do, of course,
  212.  but we've isolated it. We have one module that will access the screen memory
  213.  map directly. And even the BIOS dependencies are all in one place."
  214.  
  215.  The move from a character environment (MS-DOS 3.x) to a graphics environment
  216.  (Microsoft Windows and the OS/2 Presentation Manager) is a considerably
  217.  more ambitious undertaking. Schwartz talked about the whys and hows of
  218.  this move and about how the graphics-based Paradox will look, feel, and
  219.  perform relative to the original.
  220.  
  221.  
  222.  Why Windows?
  223.  
  224.  The move to Windows now, according to Schwartz, will give Ansa advantages in
  225.  marketing and in implementation when the OS/2 Presentation Manager arrives.
  226.  
  227.  "We think it's important to put out the Windows version, not because Windows
  228.  2.0 is going to be the primary market-certainly, Presentation Manager
  229.  market will dwarf the Windows market-but because a lot of companies will be
  230.  evaluating the Presentation Manager interface through Windows 2.0. And
  231.  applications that run under Windows early on will have an advantage, given
  232.  the long evaluation cycles that companies go through."
  233.  
  234.  Second, because Windows 2.0 is here now and Presentation Manager is not,
  235.  the effort required to rethink and recode Paradox for Windows is the best
  236.  preparation Ansa can undertake for the eventual port to the Presentation
  237.  Manager-even though the API for Presentation Manager will differ
  238.  significantly from that for Windows.
  239.  
  240.  
  241.  The Good News
  242.  
  243.  How hard was the port to Windows? According to Schwartz, all but the
  244.  underlying database engine was redesigned, borrowing pieces of the
  245.  previous architecture and source code. Nevertheless, he asserts, about 50
  246.  percent of the original source code emigrated to the new world intact.
  247.  That's an off-the-cuff, work-in-progress estimate, but even if it is too
  248.  optimistic, it is still good news for Windows developers: not all programs
  249.  will have to be rewritten from the ground up to run in the new graphics
  250.  environments.
  251.  
  252.  Moreover, in Ansa's case the conversion will probably take about two person-
  253.  years of work. That's not bad, considering that the original Paradox was a
  254.  14-person-year effort. And the company has done it without hiring a lot of
  255.  specialized expertise. That's also good news, considering the current
  256.  scarcity and high market value of Macintosh and Windows programmers.
  257.  
  258.  "We tried to get some people with product-level graphics experience. We
  259.  used a headhunter for a while, but we were very unsuccessful," says
  260.  Schwartz. "And it turned out that we did very well taking good software
  261.  engineers who have general computer science sophistication and having them
  262.  come up to speed under Windows. We found that general computer science
  263.  background was the most important factor."
  264.  
  265.  
  266.  A Natural Fit
  267.  
  268.  Making Ansa's transition from characters to pixels easier was the fact that
  269.  Paradox, despite its early origins (development for the first version
  270.  began in the summer of 1981), has always been a highly visual, interactive
  271.  program. From an interface design standpoint, therefore, the transfer to
  272.  Windows entailed an expansion of ideas already in place rather than a
  273.  wholesale reconceptualization.
  274.  
  275.  "We've always had a very visual orientation, an object orientation," says
  276.  Schwartz. "And it was just sort of a natural fit."
  277.  
  278.  Data tables in the character-based Paradox, for example, are presented to
  279.  the user in the form of visual constructs that look much like windows,
  280.  except that the borders are made of text-graphic characters and don't go all
  281.  the way around (see Figure 1). Like a frame in Framework or a screenful of a
  282.  spreadsheet, these objects act as portholes onto larger expanses of data,
  283.  which Paradox calls images.
  284.  
  285.  Paradox presents the user with a workspace, much as Windows does, on which
  286.  he can keep several images visible at once. And though it doesn't support a
  287.  mouse, the character-based product lets the user manipulate the sizes and
  288.  locations of images, and the width of columns within them, by positioning
  289.  a highlight in the right hot spot and pressing cursor keys. This ability to
  290.  interact immediately with a data construct, instead of having to issue a
  291.  menu command and describe what one wants, is what Schwartz calls "direct
  292.  operability," an important component of the original Paradox interface.
  293.  
  294.  
  295.  Visual Interaction
  296.  
  297.  "The idea was──as much as possible, anywhere we could──to allow visual
  298.  interaction, rather than descriptive interaction," says Schwartz. "Instead
  299.  of asking the user to explain or describe something, we asked him to show it
  300.  to us. That's very compatible with moving to the Windows environment."
  301.  
  302.  Paradox under Windows will look quite similar at first glance to the
  303.  original version (see Figure 2), except that the Lotus-style menu gives way
  304.  to drop-down menus, the text comes up black-on-white, and the images are
  305.  fully formed document windows, with title bars, scroll bars, control menus,
  306.  and sizing icons. The direct operability will still be there, but there'll
  307.  be more of it. And, of course, there will be mouse support.
  308.  
  309.  One new area of direct operability in Windows Paradox is in the
  310.  specification of sorts. In the character-based version, the user indicated
  311.  which fields he wanted to be sort keys and in which direction he wanted a
  312.  field sorted by typing a field number followed by an "a" for ascending or a
  313.  "d" for descending (see Figure 3).
  314.  
  315.  The Windows version (see Figure 4) simplifies this process and makes it less
  316.  analytical by letting the user double-click directly on the name of each
  317.  sort key field. An arrow indicating sort direction (ascending, by default)
  318.  will appear to the left of the chosen field name; to switch directions, the
  319.  user merely toggles the arrow by double-clicking on it.
  320.  
  321.  The notion of clicking on a value to toggle it or to select from available
  322.  values appears elsewhere in Windows Paradox. In creating or restructuring a
  323.  table, for example, where the user is asked for a data type, he can click
  324.  the mouse to get a menu showing available types. Similarly, in the
  325.  create/restructure subsystem, a double-click on a field name will summon
  326.  a dialog box, allowing the user to inspect current validity-check
  327.  definitions or create new ones.
  328.  
  329.  
  330.  Implementation Advantages
  331.  
  332.  Describing the conversion effort, Schwartz downplays the fearsome Windows
  333.  learning curve, emphasizing instead the implementation advantages
  334.  conferred by the graphics environment. In particular, he cites the greater
  335.  degree of independence among the various objects with which the user
  336.  interacts.
  337.  
  338.  In the Windows environment, every table in the workspace, as well as every
  339.  form, query, and report specification, lives in its own window and has its
  340.  own message handler. That message handler is responsible for knowing only
  341.  about itself, how to repaint itself and how to update itself. In the
  342.  character-based version, more attention had to be paid to the "global
  343.  state," that is, to the ways in which changes in a table would affect
  344.  everything else in the workspace.
  345.  
  346.  "At the implementation level," says Schwartz, "that means that your code
  347.  can have an architecture such that everything is purely local to each
  348.  image. So you can have a local handler that knows how to resize a window or
  349.  how much of it to repaint. If anything in the size of that window is
  350.  affected, Windows takes care of sending messages to the other windows to let
  351.  them know that some other portion needs to be repainted."
  352.  
  353.  
  354.  Debugging Difficulties
  355.  
  356.  According to Schwartz, one fairly serious hindrance to the development of
  357.  Windows Paradox was the lack of debugging tools for use in the Windows
  358.  environment.
  359.  
  360.  "It's a harder problem anyway," Schwartz says, "because there's more going
  361.  behind the scenes in the Windows environment." The movability and
  362.  discardability of Windows object code complicates matters, making bugs
  363.  harder to find. Bugs tend to turn up later in the game as the result of
  364.  stress on the system because a piece of code has been moved in memory.
  365.  
  366.  The current version of CodeView(R) doesn't work in the Windows environment,
  367.  though Microsoft is working on a Windows-specific version. Symdeb is
  368.  provided, but Ansa didn't use it extensively, preferring instead to rely on
  369.  algorithmic-level debugging by embedding printf statements to test values
  370.  of variables at various points in the program.
  371.  
  372.  "In our design environment, we have a monochrome display hooked up as well
  373.  as an EGA. We've hooked things up so that we can put printfs in the code and
  374.  have them scroll out on the monochrome display. You get the effect of
  375.  traditional debugging preserved in the Windows environment."
  376.  
  377.  Making debugging even more difficult was the fact that Ansa was working with
  378.  early prerelease versions of Windows and the Windows development tools (the
  379.  project began last April). Programmers were sometimes hard pressed to tell
  380.  which problems they should attribute to their own code, which to the
  381.  compiler, and which to bugs in the underlying environment.
  382.  
  383.  
  384.  User Advantages
  385.  
  386.  The independence of screen objects provides several benefits for users.
  387.  Most importantly, it means that users can have more things going on at
  388.  once and still keep track of everything. Instead of being limited to the
  389.  number of images that will fit one after the other on screen, the user can
  390.  load up the screen with overlapping windows, thereby achieving whatever
  391.  level of information density he finds most appropriate.
  392.  
  393.  This facilitates such activities as moving from one table to another,
  394.  copying data or structures from one table to another, discovering
  395.  correlations between one set of data and another, and so on. It also means
  396.  that Windows Paradox will appear somewhat less "modal" to users than its
  397.  text-based ancestor. There are places in the current program where a user
  398.  has to drop one context, if only briefly, to work in another. The Windows
  399.  version will minimize the effects of such breaks in continuity.
  400.  
  401.  For example, in the character-based version, if the user wants to modify a
  402.  data-entry form on the fly while entering data, he issues a command to
  403.  summon the forms subsystem. The data and form he was working on disappear
  404.  at that point, becoming visible again only after he's finished changing
  405.  the form spec. In the Windows version, the form subsystem will appear in a
  406.  child window, with the data remaining visible in the parent window.
  407.  
  408.  Other benefits the Windows Paradox user will find include the following:
  409.  
  410.    ■  The Windows version will automatically support any high-resolution
  411.       large-screen displays supported by Windows itself.
  412.  
  413.    ■  The report subsystem will offer enhanced font support and will accept
  414.       bit-mapped images by way of the Windows clipboard. Users can dress up
  415.       Paradox reports with company logos, signatures, clip art, and the
  416.       like. Windows Paradox will not accept bit-mapped images as data
  417.       types, although Schwartz concedes that that's "a natural direction"
  418.       for the product to evolve in.
  419.  
  420.    ■  Windows Paradox can exchange data with other Windows applications by
  421.       way of dynamic data exchange (DDE). At the Microsoft Excel
  422.       announcement, Ansa showed an early version of Paradox running as a
  423.       client of Microsoft Excel, with Paradox started up by means of the
  424.       macro language and transferring data to Microsoft Excel via DDE (see
  425.       Figure 5). The finished Windows version of Paradox will allow a
  426.       reversal of this scenario: you can start up and control other
  427.       applications by means of scripts written in the Paradox application
  428.       language (PAL).
  429.  
  430.  
  431.  Performance Hits
  432.  
  433.  The picture sounds rosy, but Schwartz concedes that the advantages of the
  434.  Windows environment will exact a few performance penalties. The hits, he
  435.  suggests, will come less from Windows' need to represent text in graphics
  436.  mode than from its propensity to swap program modules in and out of memory.
  437.  
  438.  Because the program was still under development at this writing, Ansa was
  439.  not prepared to supply benchmark data. However, Schwartz maintains the
  440.  company is "quite happy with the Paradox performance on 10-MHz 286s and
  441.  above." And, he says, the SMARTDrive program, a Microsoft-supplied disk-
  442.  caching utility tailored to enhance the performance of Windows, speeds
  443.  things up significantly on all machines.
  444.  
  445.  
  446.  Fears Allayed
  447.  
  448.  What messages would Ansa's Schwartz pass along to another Windows
  449.  application developer? The most obvious one is that the redesign of an MS-
  450.  DOS program to run under Windows is not the horrendous task it's commonly
  451.  thought to be, particularly if the program was well designed in the first
  452.  place. A second notion involves the maintenance of individuality under
  453.  conditions that seem to impose similarity.
  454.  
  455.  "Some people say that all products are going to look alike, that all the
  456.  creativity has been taken away from the designer in the Windows
  457.  environment," says Schwartz. "So much has been standardized that
  458.  everything's going to look the same and work the same. That's certainly
  459.  true at some level. Still, there is a very diverse set of ways in which one
  460.  can take advantage of the environment to present a particular application.
  461.  
  462.  "You need to concern yourself first with the core model of how an
  463.  application is to be constructed-with the user-level concepts and how you
  464.  want to present them. And only after that you look at what the Windows tools
  465.  are and how you'll use them. The greatest effect on the user is the
  466.  underlying model, and that is not affected at all by Windows. Windows is
  467.  just a way to present that, to communicate it more effectively to the
  468.  user."
  469.  
  470.  
  471.  ───────────────────────────────────────────────────────────────────────────
  472.  Paradox Under Microsoft(R) OS/2
  473.  ───────────────────────────────────────────────────────────────────────────
  474.  
  475.  Paradox was concieved in the days of very limited resources. Development
  476.  began in the first half of 1981, months before the announcement of the
  477.  IBM(R) PC, when 64Kb was as much memory as anyone could expect to have.
  478.  
  479.  Over the next several years, as larger machines grew commonplace, Paradox
  480.  grew in scope and ambition, so that by the time the the product was
  481.  announced in the Fall of 1885, it required 512Kb to run──practically
  482.  speaking, that meant a 640Kb computer.
  483.  
  484.  However, like a depression era parent, Paradox never forgat what it was like
  485.  to live in lean times. From the beggining, the product incorporated a
  486.  virtual memory manager that swapped data as necessary to the hard disk. One
  487.  of the programs selling points, moreover, was the heuristic approach to the
  488.  processing of relational queries. An important aspect of this process was
  489.  machine reasoning about what data was currently in memory and what was
  490.  swapped out somewhere.
  491.  
  492.  Therefore, the large address space afforded by the 80286 and 80386 chips
  493.  running in protected mode represents a liberation for Paradox, demonstrated
  494.  by the preliminary statistics shown in the accompanying charts.
  495.  
  496.  Judging by the numbers, Paradox running under the OS/2 systems in protected
  497.  mode should take care of complex queries and large scale sorts much more
  498.  quickly than the MS-DOS 3.3 version. The 80386 version will do queries about
  499.  as efficiently as the OS/2 versions and will be even more nimble at sorting.
  500.  
  501.  Perhaps the most surprosing information revealed by these early numbers is
  502.  that Paradox 2.0 running in real mode under OS/2 (in the OS/2 compatibility
  503.  box) outperforms the same product running on the same hardware under MS-DOS
  504.  3.3. According to Microsoft, most MS-DOS programs will run 5 percent slower
  505.  in the compatibility box. Ansa's Richard Schwartz attributes the improvement
  506.  to the fact that Paradox is doing a lot of random file access and is
  507.  therefore able to profit significantly from OS/'s disk caching.
  508.  
  509.  Performance statistics for the Microsoft Windows version of Paradox were not
  510.  yet available as of this writing.
  511.  
  512.  
  513.      ╔═══╗
  514.      ║   ║100─┐      93
  515.      ║   ║    │      ▄▄▄                     ▒▒ Query ░░ Sort
  516.      ║   ║    │     ░░░█
  517.      ║   ║ 80─┤  73 ░░░█            71
  518.      ║ S ║    │  ▄▄▄░░░█            ▄▄▄
  519.      ║ E ║    │ ▒▒▒█░░░█        59 ░░░█            60
  520.      ║ C ║ 60─┤ ▒▒▒█░░░█        ▄▄▄░░░█        46  ▄▄▄        48
  521.      ║ O ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█        ▄▄▄░░░█        ▄▄▄ 44
  522.      ║ N ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█ ▄▄▄
  523.      ║ D ║ 40─┤ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  524.      ║ S ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  525.      ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  526.      ║   ║ 20─┤ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  527.      ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  528.      ║   ║    │ ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█       ▒▒▒█░░░█
  529.      ╚═══╝  0─┼─▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀───┬───▒▒▒▀░░░▀─┐
  530.               Paradox 2.0    Paradox 2.0     Paradox OS/2   Paradox 386
  531.              under DOS 3.3   in the OS/2     in protected   under DOS 3.3
  532.                            Compatability Box     mode
  533.  
  534.      ════════════════════ ELAPSED TIME IN SECONDS ═══════════════════════
  535.  
  536.  Preliminary performance statistics for three new versions of Paradox, as
  537.  measured on a PS/2 Model 80. The query involved a join of a 5000-record
  538.  table and a 10,000-record table; the sort was for the 500-record table. The
  539.  figures for Paradox OS/2 and Paradox 386 may differ when those products are
  540.  shipped.
  541.  
  542.  ───────────────────────────────────────────────────────────────────────────
  543.  
  544.     ╔═P═╗
  545.     ║ E ║    60─┐                                              53%
  546.     ║ R ║       │     ▒▒ Query                                 ▄▄▄▄
  547.     ║ F ║       │                                             ░░░░█
  548.     ║ O ║    50─┤     ░░ Sort                                 ░░░░█
  549.     ║ R ║       │                                             ░░░░█
  550.     ║ M ║       │                      37%                    ░░░░█
  551.     ║ A ║    40─┤                      ▄▄▄▄ 35%           34% ░░░░█
  552.     ║ N ║       │                     ▒▒▒▒█ ▄▄▄▄          ▄▄▄▄░░░░█
  553.     ║ C ║       │                     ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  554.     ║ E ║    30─┤        24%          ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  555.     ║   ║       │        ▄▄▄▄         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  556.     ║ I ║       │   19% ░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  557.     ║ M ║    20─┤   ▄▄▄▄░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  558.     ║ P ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  559.     ║ R ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  560.     ║ O ║    10─┤  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  561.     ║ V ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  562.     ║ E ║       │  ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█         ▒▒▒▒█░░░░█
  563.     ║ M ║     0─┴──▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀────┬────▒▒▒▒▀░░░░▀──┐
  564.     ╚═T═╝      Paradox 2.0 in the   Paradox OS/2 in    Paradox 386 under
  565.                OS/2 compatability   protected mode     DOS 3.3
  566.                box
  567.  
  568.  Performance improvements of three new versions of Paradox over Paradox 2.0
  569.  running in MS-DOS 3.3. These figures are preliminary and may not reflect
  570.  actual performance when the products are shipped.
  571.  
  572.  ████████████████████████████████████████████████████████████████████████████
  573.  
  574.  Converting Windows Applications For Microsoft(R) OS/2 Presentation Manager
  575.  
  576.  Micheal Geary
  577.  
  578.  The Microsoft(R) OS/2 Presentation Manager represents the marriage of two
  579.  technologies: the Windows operating environment and the protected-mode OS/2
  580.  operating systems. In any wedding, it's traditional to have something old,
  581.  something new, something borrowed, and something blue, and Presentation
  582.  Manager is no exception.
  583.  
  584.  Something old is Microsoft Windows. In a sense, Presentation Manager is
  585.  really Windows. Although there have been a number of changes from the real-
  586.  mode (MS-DOS(R)) version of Windows, nearly all Windows programming concepts
  587.  carry over directly to the new environment: message-driven programming,
  588.  window functions, resources, and so forth. If you know how to program
  589.  Windows, Presentation Manager will be the icing on the wedding cake.
  590.  
  591.  Something new is the protected-mode hardware and OS/2 operating systems. It
  592.  would not be far from the truth to say this is the environment Windows was
  593.  really meant for. OS/2 offers true preemptive multitasking, unlike the
  594.  "round-robin" multitasking of real-mode Windows. Applications no longer have
  595.  to wait for each other to yield control. Within a single application, you
  596.  can make good use of OS/2's multiple threads of execution, assigning to
  597.  different windows their own threads. The protected-mode hardware greatly
  598.  expands memory addressing capability and eliminates the need for some of the
  599.  software memory management done by real-mode Windows.
  600.  
  601.  Something borrowed is the new Graphics Programming Interface (GPI). Adapted
  602.  from the mainframe graphics GDDM (Graphics Data Display Manager) and GCP
  603.  (Graphics Control Program) standards, GPI replaces Windows' Graphics Device
  604.  Interface (GDI) with a richer, more versatile set of graphics functions.
  605.  
  606.  Something blue, as you've already guessed, is IBM. Presentation Manager
  607.  plays a crucial role in IBM's Systems Application Architecture (SAA) as the
  608.  user interface portion of SAA, at least for machines with graphics
  609.  capabilities. IBM has an ambitious goal for SAA: a common user interface and
  610.  programming interface on a wide range of machines, from PCs to mainframes.
  611.  Eventually you should be able to take a Presentation Manager application and
  612.  compile it to run on any machine. For machines without graphics, a subset of
  613.  Presentation Manager functions will be provided, giving a similar user
  614.  interface, but running in a character mode environment.
  615.  
  616.  
  617.  Presentation Manager Programming
  618.  
  619.  To explore the differences and the similarities between a Presentation
  620.  Manager application and a Windows application, let's look at a sample
  621.  program called SayWhat, which is my entry in the "silliest Windows
  622.  application" contest. I started out with the Hello program that comes with
  623.  the Windows Software Development Kit and doctored it up a bit. Instead of
  624.  just displaying the "hello" message in one place, the text wanders around
  625.  the window randomly, changing its color as it moves. If you make SayWhat
  626.  into an icon, it spells out the message one letter at a time, with the
  627.  letter wandering around the icon. A control panel, a modeless dialog box,
  628.  lets you change the displayed text and adjust how fast it moves and how
  629.  often it selects a new random direction to move in.
  630.  
  631.  ───────────────────────────────────────────────────────────────────────────
  632.  The source code for SayWhat is found at the end of the text
  633.  ───────────────────────────────────────────────────────────────────────────
  634.  
  635.  SayWhat illustrates several programming techniques that are useful for
  636.  Windows and Presentation Manager. It also points out an irritating flaw in
  637.  the way many Windows applications do their screen painting and how to
  638.  improve it. A Windows version and a Presentation Manager version of SayWhat
  639.  are listed side by side for comparison.
  640.  
  641.  When comparing the listings, it is immediately apparent that the overall
  642.  structure of the two programs is identical. The most important aspects of
  643.  Windows apply equally to Presentation Manager: messages, window functions,
  644.  painting, and so on. However, at the detailed coding level we will see many
  645.  differences.
  646.  
  647.  
  648.  Getting Started
  649.  
  650.  At the beginning, both programs have a main function, but in the
  651.  Presentation Manager application it's called main( ) instead of WinMain( )
  652.  as in the Windows application. That's because a Presentation Manager
  653.  application starts out as an ordinary OS/2 application; what makes it
  654.  different is its use of the Presentation Manager API.
  655.  
  656.  The parameters passed to main are different from those that are passed to
  657.  WinMain in the Windows version. The lpszCmdLine parameter is replaced by the
  658.  more conventional, and convenient, argc and argv parameters. There is no
  659.  nCmdShow parameter. If you create your main application window as an
  660.  ordinary visible window by using the WinCreateStdWindow function, this is
  661.  taken care of for you along with the window size. If you require more direct
  662.  control over your window placement, create your main window as invisible and
  663.  then set the size and position yourself. Finally, the hPrevInstance and
  664.  hInstance parameters are missing: hPrevInstance isn't used at all and there
  665.  is nothing in Presentation Manager corresponding to the GetInstanceData
  666.  function. Under OS/2, there is no such thing as an instance in the same
  667.  sense as there is in Windows. There are only OS/2 processes, and each
  668.  process initializes its own data. Subsequent instances of an application
  669.  have exactly the same initialization as the first instance.
  670.  
  671.  
  672.  Message Loop
  673.  
  674.  The main message loop of a Presentation Manager program is nearly identical
  675.  to the main loop of a Windows program. Other than some name changes and the
  676.  use of the hAB parameter, the only difference is that the TranslateMessage
  677.  function is not used. Since this function was always required anyway, it has
  678.  been merged with WinGetMsg. Actually, the keyboard messages under
  679.  Presentation Manager have been substantially simplified. The messages
  680.  WM_KEYDOWN, along with WM_KEYUP, WM_SYSKEYDOWN, and WM_SYSKEYUP have all
  681.  been removed, and the information that they provided is incorporated into
  682.  WM_CHAR. The WM_CHAR message now provides the "raw" keyboard information
  683.  that the WM_KEYDOWN/UP messages used to give, as well as the "translated"
  684.  ASCII character code always provided by WM_CHAR.
  685.  
  686.  
  687.  Initialization
  688.  
  689.  Before the message loop starts up, we have to initialize the application and
  690.  create our main window, which is done in the SayInitApp function. The
  691.  Presentation Manager version begins with two new calls: first, it calls
  692.  WinInitialize which initializes the program for Presentation Manager and
  693.  returns a handle to an anchor block, called hAB in our code. The anchor
  694.  block isn't really used by Presentation Manager, but it's there for
  695.  compatibility with future SAA environments. However, the hAB parameter is
  696.  required on several of the Presentation Manager function calls.
  697.  
  698.  WinInitialize doesn't actually make the application become a Presentation
  699.  Manager application. In fact, kernel applications can call WinInitialize if
  700.  they want to use the local memory management or atom functions provided with
  701.  Presentation Manager. Then it calls WinCreateMsgQueue, which allocates its
  702.  message queue. Unlike Windows, in Presentation Manager you have to do this
  703.  yourself. This gives you more flexibility; you can specify the size of the
  704.  message queue or just take the default by specifying a size of 0, as SayWhat
  705.  does.
  706.  
  707.  WinCreateMsgQueue is that special call that says "yes, this is a
  708.  Presentation Manager application." It establishes this thread of execution
  709.  as a message queue thread, and if you happen to start up a Presentation
  710.  Manager application on a system that has the regular kernel shell running
  711.  instead of the Presentation Manager shell, this is the call that puts you
  712.  into graphics mode. If you have multiple threads, some of the threads may
  713.  have message queues and some may not, but any thread that wants to create
  714.  windows must call WinCreateMsgQueue since threads cannot share a queue.
  715.  
  716.  Both applications then load some string resources. This is nearly identical
  717.  in the two versions, except for the hAB parameter used in the Presentation
  718.  Manager version. Next we pick up display information: the number of colors
  719.  available, the system font size, and the screen size. In the Windows
  720.  version, you create an information context which is like a device context,
  721.  but is used only for gathering information about the device. Then you pick
  722.  up information on the system font with GetTextMetrics, and get the number of
  723.  display planes with a GetDeviceCaps call.
  724.  
  725.  The Presentation Manager version is a little different. Instead of an
  726.  information context or device context, it gets a presentation space with the
  727.  WinGetPS call. Although Presentation Manager also has device contexts, most
  728.  graphics functions use a presentation space instead, which, in a simple
  729.  application like SayWhat, can be used much as a device context would be in
  730.  the Windows version. However, the presentation space is much more powerful
  731.  and versatile; for example, it can record and later play back graphics
  732.  calls, much like a metafile. You could avoid having to process WM_PAINT
  733.  messages this way. (This approach does not enhance performance, but it can
  734.  be a programming convenience for some applications.) The details of
  735.  presentation spaces are not discussed here, since SayWhat doesn't do
  736.  anything fancy with them.
  737.  
  738.  Having obtained the presentation space handle, the Presentation Manager
  739.  version then calls GpiQueryCharBox to get the system font character size and
  740.  GpiQueryColorData to pick up the number of colors. Note that we're using the
  741.  total number of distinct solid colors here, not the number of display planes
  742.  as in the Windows version.
  743.  
  744.  
  745.  Creating a Window
  746.  
  747.  Now you can register your window class and create the main window. It is
  748.  here that you encounter a major difference between Windows and Presentation
  749.  Manager-the architecture of a standard application window.
  750.  
  751.  Under Windows, a standard application window has two main components: the
  752.  client area, which your code normally deals with, and the nonclient area,
  753.  which includes all the controls around the edge of the window; title bar,
  754.  sizing border, menu bar, and so forth. Your window function receives
  755.  messages for the client and nonclient areas, but you usually pass the
  756.  WM_NCxxxxx nonclient messages through to DefWindowProc. Although this works
  757.  for most applications, this architecture has some problems. The behavior of
  758.  all of the nonclient items is hard-coded into Windows. It is possible to
  759.  override some of this behavior by intercepting the WM_NCxxxxx messages, but
  760.  this is fairly cumbersome and very limited. There are also some strange
  761.  inconsistencies, like the fact that standard scroll bars are part of the
  762.  special nonclient area, but any non-standard scroll bars are created as
  763.  child windows and are treated differently.
  764.  
  765.  When I was coding the window editor in one of my applications, this
  766.  client/nonclient architecture was a serious obstacle. I needed complete
  767.  control over all aspects of a window's behavior, because my application lets
  768.  the user build and edit window characteristics on the fly. In design mode, I
  769.  didn't want the nonclient items to behave normally, since they were objects
  770.  being edited at that point. I finally got things to work reasonably well,
  771.  but not quite the way I wanted. For example, if the user wished to remove
  772.  the Maximize icon from a window, I would have to destroy and recreate the
  773.  entire window.
  774.  
  775.  Under Presentation Manager, this client/nonclient architecture has been
  776.  eliminated. The functionality is still there, but child windows and some new
  777.  predefined window classes implement it. All of the items that were part of
  778.  the nonclient area are now child windows, and you can do special things with
  779.  them simply by subclassing them as you would any other window. There is a
  780.  new window class, the frame window, which acts as a container for all these
  781.  child windows and has the smarts to position them appropriately according to
  782.  the window size. What used to be the client area under Windows is now itself
  783.  a child window of the frame window.
  784.  
  785.  Now, for many applications, all of this won't make much difference. You can
  786.  use the WinCreateStdWindow function to set up a standard frame window with
  787.  the desired controls around the border, and things will work pretty much as
  788.  before. The only significant difference is that some operations that were
  789.  performed with Windows function calls are now done by sending messages to
  790.  the various controls.
  791.  
  792.  However, in an application where the window frame controls are treated
  793.  specially, this new architecture makes life much easier. Subclassing a child
  794.  window is a lot more convenient than trying to fool all of the WM_NCxxxxx
  795.  messages; it's a simpler, more consistent architecture. It is even
  796.  reasonable to add more special controls to the window frame, placed anywhere
  797.  you want.
  798.  
  799.  There's another, more subtle area where the window architecture has been
  800.  cleaned up. Under Windows, every window logically has a parent window and an
  801.  owner window. The parent determines how screen space is used-a child window
  802.  is contained within its parent and clipped at the edges of the parent. The
  803.  owner determines two things; control windows (edit controls, for example)
  804.  send notification messages to their owner, and pop-up windows disappear when
  805.  their owner is minimized. These are two different steps, although Windows
  806.  combines them into a single "parent" window handle and interprets this
  807.  handle according to the window style bits. For a WS_CHILD window, the
  808.  "parent" is actually both the parent and the owner. For a WS_POPUP, the
  809.  actual parent is NULL, and the "parent" is really the owner.
  810.  
  811.  Presentation Manager separates the parent and owner window handles, letting
  812.  you specify them individually. This removes the need for the WS_CHILD,
  813.  WS_POPUP, and WS_OVERLAPPED window styles; instead, a window is just a
  814.  window. Furthermore, every window has a parent, because there is one new
  815.  window, called the desktop window, which occupies the entire screen. Every
  816.  top level window is actually a child of the desktop window.
  817.  
  818.  With all this in mind, let's look at the WinRegisterClass and
  819.  WinCreateStdWindow calls in SayWhat. You'll see that there isn't much to the
  820.  WinRegisterClass call. The WNDCLASS structure from the Windows version is
  821.  gone, because most of what it specified has migrated to the frame window
  822.  class and also to WinCreateStdWindow. One interesting new option in
  823.  WinRegisterClass is the CS_SYNCPAINT style bit. If this bit is set, then
  824.  WM_PAINT messages for windows of this class are not deferred in the
  825.  customary fashion. Instead, WM_PAINT is sent immediately whenever the window
  826.  becomes invalidated. This is how the window frame controls get repainted
  827.  immediately; they have the CS_SYNCPAINT style. In the Windows version, this
  828.  is handled specially by WM_PAINT, but under Presentation Manager, this
  829.  feature is available to any window. Since SayWhat's window painting
  830.  procedure is very simple and quite fast, CS_SYNCPAINT makes for a nice clean
  831.  display. A window class that takes longer to paint should omit this style
  832.  bit, and its windows will receive deferred WM_PAINT messages just as they do
  833.  under Windows.
  834.  
  835.  The WinCreateStdWindow call creates SayWhat's frame window, along with its
  836.  client window and all the frame controls. The frame window handle and the
  837.  client window handle are returned by this call, the latter through the
  838.  pointer in the last parameter. The window style bits look pretty familiar,
  839.  but many of them have FS_ names instead of WS_ names because they are really
  840.  just indicators to the frame window telling it which child windows to
  841.  create. Also, there's no size or position information in this call. The
  842.  trick is that if you give the window the WS_VISIBLE style, it will
  843.  automatically be assigned the appropriate default size and position.
  844.  Alternatively, you can create it as invisible and place it where you want
  845.  (and then make it visible) explicitly with the call WinSetWindowPos.
  846.  
  847.  WinCreateStdWindow is just a convenience for creating standard frame
  848.  windows. There is also a generic WinCreateWindow that can create any kind of
  849.  window, giving you total control over how the window is set up. You would
  850.  use this function to create additional child windows or any other kind of
  851.  special window.
  852.  
  853.  
  854.  Window Functions
  855.  
  856.  Now that our window has been created, it's time to look at the window
  857.  function, SayWhatWndProc. You'll notice that the window functions in the two
  858.  versions are very similar. As I mentioned before, all of the keyboard input
  859.  is done under a WM_CHAR message, not under WM_KEYDOWN as in the Windows
  860.  version, but otherwise it's essentially the same. Mouse input is nearly
  861.  identical also, except that the message names have been changed to avoid the
  862.  inconsistency of the right button sending a message called WM_LBUTTONUP/DOWN
  863.  when the buttons have been swapped-the main button is instead WM_BUTTON1.
  864.  
  865.  The other messages processed by SayWhatWndProc are also extremely similar;
  866.  WM_CREATE, WM_DESTROY, WM_SIZE, WM_PAINT, WM_TIMER, and WM_COMMAND. One very
  867.  apparent difference is that there is no corresponding wParem = = SIZEICONIC,
  868.  but there is a new WM_MINMAX message that provides the same information.
  869.  WM_MINMAX is thus intercepted and processed to find out when the window is
  870.  being minimized or restored to the normal state.
  871.  
  872.  There are some differences in the message parameters for all messages,
  873.  though. Instead of a 16-bit parameter and a 32-bit parameter, wParam and
  874.  lParam, there are two 32-bit parameters; lParam1 and lParam2. This allows a
  875.  little more information to be passed along with messages, but the real
  876.  reason for this change was for those messages that pass an additional window
  877.  handle in wParam, such as WM_VSCROLLCLIPBOARD. Under Presentation Manager,
  878.  window handles and most other handles are 32 bits, not 16 bits, so the
  879.  wParam had to be expanded to permit this. The hWnd parameter to the window
  880.  function is also a 32-bit parameter. Watch out for this one if you have any
  881.  code that assumes handles are 16 bits.
  882.  
  883.  
  884.  Window Painting
  885.  
  886.  The SayWhatPaint function handles window painting in both versions. It is
  887.  rather similar in the two versions, although the Presentation Manager
  888.  version has no PAINTSTRUCT. Instead, the WinBeginPaint function returns a
  889.  presentation space handle and gives you the update rectangle as a separate
  890.  parameter. In addition, it makes use of GpiSetColor instead of using
  891.  SetTextColor, and uses GpiCharStringAt instead of TextOut. The same work is
  892.  being performed in both cases.
  893.  
  894.  There's a little twist in SayWhat's painting logic in both versions. When
  895.  you run SayWhat, you will notice that the text wanders around the window
  896.  cleanly. There is no flicker, even though the text is repainted from scratch
  897.  each time. Many Windows applications have a little bit of flicker when they
  898.  paint into their window, which is caused by the usual practice of erasing
  899.  the background completely and then painting the nonblank portions. That's
  900.  fine when you paint a window for the first time, but if you want to repaint
  901.  a portion of the window, there is a split second where the background is
  902.  blanked out.
  903.  
  904.  You've probably seen this effect running Notepad or Write. If you type
  905.  quickly in either program, the line you are on will flicker a little instead
  906.  of painting cleanly. In SayWhat, you can see the same problem by selecting
  907.  the Flicker option on the control dialog box. It's even worse here because
  908.  the window gets repainted so frequently.
  909.  
  910.  How does SayWhat avoid this flicker in its normal mode? Simple: it doesn't
  911.  erase the background underneath the text it's about to paint. It does erase
  912.  any other portions of the background if needed, but where the text itself
  913.  will go, it just writes the text without erasing first. This works because
  914.  text is normally written opaquely; it completely covers whatever is
  915.  underneath it.
  916.  
  917.  There are two different painting routines to illustrate this in
  918.  SayWhatPaint. If the bCleanPaint variable is FALSE, it uses the common,
  919.  flickery painting method, that is, it first erases the entire background and
  920.  then draws the text. If bCleanPaint is TRUE, it draws the text and erases
  921.  only the remaining portions of the background.
  922.  
  923.  It may seem silly to get worked up about this, but fixing it dramatically
  924.  improves the appearance of an application. I know this from my own
  925.  experience: the outline editor in my own application originally had a lot of
  926.  irritating flicker, which went away when I converted it to use the clean
  927.  painting method. And, even though it took a little extra code in the paint
  928.  function, it actually reduced the overall amount of code and complexity. The
  929.  flicker had been so annoying that I was ready to put in all kinds of special
  930.  cases to make sure I never invalidated more of the window than was
  931.  necessary, but with the clean painting method, it no longer matters.
  932.  Painting is totally invisible to the user now, so if I invalidate a little
  933.  too much, no one is the wiser.
  934.  
  935.  
  936.  Dialog Boxes
  937.  
  938.  SayWhat has two dialog boxes: a modal About box and a modeless control
  939.  panel, which are both created under the WM_COMMAND case in SayWhatWndProc.
  940.  The code in both versions is similar except that MakeProcInstance is not
  941.  necessary in the protected-mode environment. OS/2 and the memory management
  942.  hardware set up the proper data segment for "call-back" functions such as
  943.  dialog functions. This is a blessing for anyone who has struggled with
  944.  mysterious system crashes caused by leaving out a MakeProcInstance under
  945.  Windows. However, you still need to remember to EXPORT your call back
  946.  functions and window functions, as was the case before.
  947.  
  948.  There is one important difference in the implementation of the dialog box
  949.  functions themselves. Under Windows, a dialog box function is not coded like
  950.  a normal window function. In fact, the actual window function for all dialog
  951.  boxes is a private function inside Windows called DlgWndProc, which calls
  952.  your dialog function before doing its own message switch statement and if it
  953.  returns nonzero, it skips its own message processing. One problem with this
  954.  approach is that it's a little confusing to have dialog functions work
  955.  differently from normal window functions. Another problem is that it is
  956.  impossible for a dialog function to return a value back to a SendMessage
  957.  call. The return value you give isn't returned back through SendMessage;
  958.  DlgWndProc simply returns zero. (Odd as it may seem, DlgWndProc treats the
  959.  WM_CTLCOLOR message as a special case and does return the value you
  960.  provide.)
  961.  
  962.  Under Presentation Manager, a dialog function operates like any other window
  963.  function. Its return value is the real return value, and there is a default
  964.  message function, WinDefDlgProc, that you call for messages that you don't
  965.  process yourself. SayAboutDlgProc illustrates this difference and, like most
  966.  About boxes, doesn't do much else except close itself when you hit the OK
  967.  button.
  968.  
  969.  The other dialog box, SayWhat's control panel, is a little more interesting.
  970.  This dialog box has no owner window (parent window under Windows). Usually,
  971.  you specify an owner window when you create a dialog box, which causes three
  972.  things to happen: the box is automatically positioned relative to the
  973.  parent, it is always on top of the parent, and it disappears if the parent
  974.  is made into an icon. However, in SayWhat, I didn't want the latter two
  975.  actions; I wanted the box to remain visible if SayWhat's window itself is
  976.  made into an icon so that you could fiddle with the parameters while the
  977.  icon is displayed. I also wanted to be able to put SayWhat's main window on
  978.  top of the dialog box.
  979.  
  980.  Creating the dialog box with a NULL owner takes care of this; however, I
  981.  still wanted the box to be positioned relative to the main window instead of
  982.  being relative to the screen. So, the WM_INITDIALOG code does this
  983.  positioning explicitly, using ClientToScreen calls in the Windows version
  984.  and a WinMapWindowPoints call in the Presentation Manager version.
  985.  WinMapWindowPoints is a handy function, which replaces ClientToScreen and
  986.  ScreenToClient, since you can specify a source window, a destination window,
  987.  or both. It will also map as many points as you want in one call. In this
  988.  program it maps two points, that is, one rectangle.
  989.  
  990.  After the mapping, you have to make sure that you haven't moved the dialog
  991.  box partially offscreen, so you check it against the screen boundaries and
  992.  adjust it back if necessary. Then, you call SetWindowPos or WinSetWindowPos
  993.  to set the new position. This function works a little differently under
  994.  Presentation Manager. If you have used SetWindowPos in Windows 2.0, you know
  995.  it can do a number of things at once, such as move the window, resize it, or
  996.  change the window ordering. The default is for it to do all of these, and in
  997.  the last parameter you tell it what you do not want it to do.
  998.  WinSetWindowPos does the same, except the last parameter tells it what you
  999.  do want it to do-just the opposite.
  1000.  
  1001.  Note that the y parameter of WinSetWindowPos takes the window bottom, not
  1002.  the window top. This shows one subtle difference between Windows and
  1003.  Presentation Manager: vertical coordinates run from bottom to top, not top
  1004.  to bottom, that is, position (0,0) is the lower-left corner of a window or
  1005.  the screen, not the upper-left corner. To be consistent with this, a
  1006.  rectangle structure is now stored in the order (left, bottom, right, top)
  1007.  instead of (left, top, right, bottom). If you're familiar with the various
  1008.  GDI mapping modes available in Windows, you have probably noticed that they
  1009.  all run from bottom to top except for MM_TEXT, which runs from top to
  1010.  bottom. This change in Presentation Manager removes that inconsistency──all
  1011.  coordinates normally run the same direction now──but it obviously requires
  1012.  some conversion effort. You can instruct Presentation Manager to use the
  1013.  top-to-bottom coordinates within a window, so that could help in converting
  1014.  existing applications.
  1015.  
  1016.  After positioning the dialog box, the WM_INITDIALOG code initializes its
  1017.  control windows. One item of interest here is the scroll bar initialization
  1018.  in SayInitBar. The Windows version calls two scroll bar functions,
  1019.  SetScrollRange and SetScrollPos, to initialize the scroll bar. Presentation
  1020.  Manager deals with these two at the same time, with a single
  1021.  SBM_SETSCROLLBAR message that sets the position and range. Also, you will
  1022.  find throughout Presentation Manager that this is performed with a message
  1023.  instead of a special function; many of the special-purpose functions for
  1024.  control windows have been removed in favor of messages. If you prefer the
  1025.  approach of calling functions, you can always write your own equivalent
  1026.  functions that send the appropriate messages.
  1027.  
  1028.  The other interesting messages in SayWhatDlgProc are WM_COMMAND and
  1029.  WM_HSCROLL. The latter message gets sent for activity in either of the
  1030.  dialog box's scroll bars, and it calls SayDoBarMsg to set the new scroll bar
  1031.  position and to set the corresponding edit control value. However, in
  1032.  Presentation Manager this message passes you the child window ID of the
  1033.  scroll bar control instead of the window handle. This is convenient, since
  1034.  you need the window ID to tell which scroll bar you're dealing with. The
  1035.  WM_COMMAND message is sent when the user clicks any of the pushbuttons or
  1036.  radio buttons in the dialog box. (It also gets sent when you type in one of
  1037.  the edit controls, but SayWhat ignores it.) The WM_COMMAND processing is
  1038.  nearly identical in the two versions, except that the IsDlgButtonChecked
  1039.  function call is replaced with a BM_QUERYCHECK message in Presentation
  1040.  Manager.
  1041.  
  1042.  
  1043.  Conclusion
  1044.  
  1045.  As you can see, converting a Windows application to Presentation Manager
  1046.  isn't trivial, but it is pretty straightforward. The similarities between
  1047.  the two systems far outweigh the differences. If you're fluent in Windows
  1048.  programming, you've already done the hard part, and you know how different a
  1049.  Windows application is from a conventional MS-DOS or OS/2 program. If you
  1050.  haven't gotten started with Windows yet, well.... What's that I hear in the
  1051.  distance? Yes, it's wedding bells. Don't be late!
  1052.  
  1053.  ───────────────────────────────────────────────────────────────────────────
  1054.  Source code in this article is referenced W for Windows programs and PM for
  1055.  Presentation Manager programs.
  1056.  ───────────────────────────────────────────────────────────────────────────
  1057.  
  1058.  
  1059.  Figure 1W:  SAYWHAT is the MAKE File for SAYWHAT
  1060.  
  1061.  #  SAYWHAT
  1062.  #  MAKE file for SAYWHAT (Windows version)
  1063.  
  1064.  sw.obj:  sw.c  sw.h
  1065.      cl -c -AS -DLINT_ARGS -Gcsw -Oas -W3 -Zdp sw.c
  1066.  
  1067.  sw.res:  sw.rc  sw.h
  1068.      rc -r sw.rc
  1069.  
  1070.  saywhat.exe:  sw.obj  sw.res  sw.def
  1071.      link4 sw, saywhat/align:16, saywhat/map/line, slibw, sw.def
  1072.      mapsym saywhat
  1073.      rc sw.res saywhat.exe
  1074.  
  1075.  
  1076.  Figure 1PM:  SAYWHAT is the MAKE File for SAYWHAT
  1077.  
  1078.  # SAYWHATP
  1079.  # MAKE file for SAYWHAT (Presentation Manager version)
  1080.  
  1081.  swp.obj:    swp.c  swp.h
  1082.      cl -c -AS -DLINT_ARGS -G2csw -Oat -W3 -Zp swp.c
  1083.  
  1084.  swp.res:    swp.rc  swp.h
  1085.      rc -r swp.rc
  1086.  
  1087.  saywhatp.exe:  swp.obj  swp.res  swp.def
  1088.      link @swp.lnk
  1089.      mapsym saywhat
  1090.      rc swp.res saywhat.exe
  1091.  
  1092.  
  1093.  Figure 2W:
  1094.  
  1095.  Note that there is no Windows equivalent for SAYWHATP.LNK, the LINK file
  1096.  for the PM version of SAYWHAT.
  1097.  
  1098.  
  1099.  Figure 2PM:  SWP.LNK is the Link File for SAYWHAT
  1100.  
  1101.  SWP.LNK
  1102.  
  1103.  swp
  1104.  saywhatp/align:16
  1105.  saywhatp/map
  1106.  wincalls doscalls mlibc286/NOD
  1107.  swp.def
  1108.  
  1109.  
  1110.  Figure 3W:  SW.DEF is the Module Definition File for SAYWHAT
  1111.  
  1112.  ; SW.DEF
  1113.  ; SW.DEF - Module definition file for SAYWHAT (Windows version)
  1114.  
  1115.  
  1116.  NAME    SayWhat
  1117.  
  1118.  
  1119.  DESCRIPTION 'Say What!'
  1120.  
  1121.  
  1122.  STUB    'WINSTUB.EXE'
  1123.  
  1124.  CODE    MOVEABLE
  1125.  DATA    MOVEABLE MULTIPLE
  1126.  
  1127.  HEAPSIZE  128
  1128.  STACKSIZE 4096
  1129.  
  1130.  EXPORTS
  1131.      SayAboutDlgProc     @1
  1132.      SayWhatDlgProc      @2
  1133.      SayWhatWndProc      @3
  1134.  
  1135.  
  1136.  Figure 3PM:  SWP.DEF is the Module Definition File for SAYWHAT
  1137.  
  1138.  ; SWP.DEF
  1139.  ; SWP.DEF - Module definition file for SAYWHAT (PM version)
  1140.  
  1141.  NAME    SayWhat
  1142.  
  1143.  DESCRIPTION 'Say What!'
  1144.  
  1145.  STUB    'OS2STUB.EXE'
  1146.  
  1147.  CODE    MOVEABLE
  1148.  DATA    MOVEABLE MULTIPLE
  1149.  
  1150.  HEAPSIZE  128
  1151.  STACKSIZE 8192
  1152.  
  1153.  EXPORTS
  1154.      SayAboutDlgProc     @1
  1155.      SayWhatDlgProc      @2
  1156.      SayWhatWndProc      @3
  1157.  
  1158.  
  1159.  Figure 4W:  SW.RC is the Resource File for SAYWHAT
  1160.  
  1161.  SW.RC
  1162.  
  1163.  #include <style.h>
  1164.  #include "sw.h"
  1165.  
  1166.  STRINGTABLE
  1167.  BEGIN
  1168.      STR_NAME,    "SayWhat!"
  1169.      STR_TITLE,   "Say What!"
  1170.      STR_WHAT,    "Hello Windows!"
  1171.  END
  1172.  
  1173.  SayWhat!    MENU
  1174.  BEGIN
  1175.  
  1176.    POPUP "&Say"
  1177.    BEGIN
  1178.      MENUITEM "&What..."                 , CMD_WHAT
  1179.      MENUITEM SEPARATOR
  1180.      MENUITEM "E&xit"                    , CMD_EXIT
  1181.      MENUITEM SEPARATOR
  1182.      MENUITEM "A&bout SayWhat!..."       , CMD_ABOUT
  1183.    END
  1184.  
  1185.  END
  1186.  
  1187.  DLG_ABOUT DIALOG 19, 17, 130, 83
  1188.  STYLE WS_DLGFRAME | WS_POPUP
  1189.  BEGIN
  1190.    CTEXT "Microsoft Windows",   -1,  0,  8, 127,  8
  1191.    CTEXT "Say What!",           -1,  0, 18, 127,  8
  1192.    CTEXT "Version 1.00",        -1,  0, 30, 127,  8
  1193.    CTEXT "By Michael Geary",    -1,  0, 44, 129,  8
  1194.    DEFPUSHBUTTON "Ok",        IDOK, 48, 62,  32, 14
  1195.  END
  1196.  
  1197.  DLG_WHAT DIALOG 49, 41, 177, 103
  1198.  CAPTION "Say What!"
  1199.  STYLE WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_POPUP
  1200.  BEGIN
  1201.   CONTROL "Say &What:"   -1,            "Static",    SS_LEFT  | WS_GROUP,
  1202.   CONTROL ""             ITEM_WHAT,     "Edit",      ES_LEFT  | ES_AUTOHSCROLL
  1203.   CONTROL "&Time Delay:" -1,            "Static",    SS_LEFT,
  1204.   CONTROL ""             ITEM_INTBAR,   "ScrollBar", SBS_HORZ | WS_TABSTOP,
  1205.   CONTROL ""             ITEM_INTERVAL, "Edit",      ES_LEFT  | WS_BORDER | WS
  1206.   CONTROL "&Stability:"  -1,            "Static",    SS_LEFT,
  1207.   CONTROL ""             ITEM_DISTBAR,  "ScrollBar", SBS_HORZ | WS_TABSTOP,
  1208.   CONTROL ""             ITEM_DISTANCE, "Edit",      ES_LEFT | WS_BORDER | WS_
  1209.   CONTROL "Painting:"    -1,            "Static",    SS_LEFT,
  1210.   CONTROL "&Clean"       ITEM_CLEAN,    "Button",    BS_AUTORADIOBUTTON | WS_G
  1211.   CONTROL "&Flicker"     ITEM_FLICKER,  "Button",    BS_AUTORADIOBUTTON,
  1212.   CONTROL "Enter"        IDOK,          "Button",    BS_DEFPUSHBUTTON | WS_GRO
  1213.   CONTROL "Esc=Close"    IDCANCEL,      "Button",    BS_PUSHBUTTON | WS_TABSTO
  1214.  END
  1215.  
  1216.  
  1217.  Figure 4PM:  SWP.RC is the Resource File for SAYWHAT
  1218.  
  1219.  SWP.RC
  1220.  
  1221.  #include <style.h>
  1222.  #include "sw.h"
  1223.  
  1224.  STRINGTABLE
  1225.  {
  1226.    STR_NAME,    "SayWhat!"
  1227.    STR_TITLE,   "Say What!"
  1228.    STR_WHAT,    "Hello Windows!"
  1229.  }
  1230.  
  1231.  MENU SayWhat!
  1232.  {
  1233.    SUBMENU "~Say",                  -1
  1234.    {
  1235.      MENUITEM "~What...",           CMD_WHAT
  1236.      MENUITEM SEPARATOR
  1237.      MENUITEM "E~xit",              CMD_EXIT
  1238.      MENUITEM SEPARATOR
  1239.      MENUITEM "A~bout SayWhat!...", CMD_ABOUT
  1240.    }
  1241.  }
  1242.  
  1243.  DLGTEMPLATE DLG_ABOUT
  1244.  {
  1245.    DIALOG "", DLG_ABOUT, 19, 17, 130, 83, FS_DLGBORDER |
  1246.  WS_SAVEBITS | WS_VISIBLE
  1247.    {
  1248.      CTEXT "Microsoft Windows",   -1,  0,  8, 127,  8
  1249.      CTEXT "Say What!",           -1,  0, 18, 127,  8
  1250.      CTEXT "Version 1.00",        -1,  0, 30, 127,  8
  1251.      CTEXT "By Michael Geary",    -1,  0, 44, 129,  8
  1252.      DEFPUSHBUTTON "Ok",        IDOK, 48, 62,  32, 14
  1253.    }
  1254.  }
  1255.  
  1256.  DLGTEMPLATE DLG_WHAT
  1257.  {
  1258.    DIALOG "Say What!", DLG_WHAT, 49, 41, 177, 103,
  1259.  FS_TITLEBAR | FS_SYSMENU | FS_BORDER | WS_VISIBLE
  1260.    {
  1261.    CONTROL "Say ~What:"   -1,              8, 10,  40,  8, WC_STATIC,    SS_LE
  1262.    CONTROL ""             ITEM_WHAT,      56,  8, 112, 12, WC_EDIT,      ES_LE
  1263.    CONTROL "~Time Delay:" -1,              8, 28,  53,  9, WC_STATIC,    SS_LE
  1264.    CONTROL ""             ITEM_INTBAR,    56, 28,  88,  8, WC_SCROLLBAR, SBS_H
  1265.    CONTROL ""             ITEM_INTERVAL, 152, 26,  16, 12, WC_EDIT,      ES_LE
  1266.    CONTROL "~Stability:"  -1,              8, 46,  56,  9, WC_STATIC,    SS_LE
  1267.    CONTROL ""             ITEM_DISTBAR,   56, 46,  88,  8, WC_SCROLLBAR, SBS_H
  1268.    CONTROL ""             ITEM_DISTANCE, 152, 44,  16, 12, WC_EDIT,      ES_LE
  1269.    CONTROL "Painting:"    -1,              8, 64,  40,  8, WC_STATIC,    SS_LE
  1270.    CONTROL "~Clean"       ITEM_CLEAN,     56, 62,  36, 12, WC_BUTTON,    BS_AU
  1271.    CONTROL "~Flicker"     ITEM_FLICKER,   96, 62,  42, 12, WC_BUTTON,    BS_AU
  1272.    CONTROL "Enter"        IDOK,           24, 82,  48, 14, WC_BUTTON,    BS_DE
  1273.    CONTROL "Esc=Close"    IDCANCEL,      104, 82,  48, 14, WC_BUTTON,    BS_PU
  1274.    }
  1275.  }
  1276.  
  1277.  
  1278.  Figure 5W:  SW.H is the C Header file for SAYWHAT
  1279.  
  1280.  /* SW.H */
  1281.  /* SW.H - C header file for SAYWHAT (Windows version) */
  1282.  
  1283.  /* String table constants */
  1284.  
  1285.  #define STR_NAME        101
  1286.  #define STR_TITLE       102
  1287.  #define STR_WHAT        103
  1288.  
  1289.  /* Menu command IDs */
  1290.  
  1291.  #define CMD_ABOUT       201
  1292.  #define CMD_EXIT        202
  1293.  #define CMD_WHAT        203
  1294.  
  1295.  /* Dialog box resource IDs */
  1296.  
  1297.  #define DLG_ABOUT       301
  1298.  #define DLG_WHAT        302
  1299.  
  1300.  /* 'What...' dialog box item IDs */
  1301.  
  1302.  #define ITEM_WHAT       401
  1303.  #define ITEM_INTBAR     402
  1304.  #define ITEM_INTERVAL   403
  1305.  #define ITEM_DISTBAR    404
  1306.  #define ITEM_DISTANCE   405
  1307.  #define ITEM_CLEAN      406
  1308.  #define ITEM_FLICKER    407
  1309.  
  1310.  /* Timer IDs */
  1311.  
  1312.  #define TIMER_MOVE      501
  1313.  #define TIMER_CHAR      502
  1314.  
  1315.  
  1316.  Figure 5PM:  SWP.H is the C Header File for SAYWHAT
  1317.  
  1318.  /* SWP.H */
  1319.  /* SWP.H - C header file for SAYWHAT (PM version) */
  1320.  
  1321.  /* String table constants */
  1322.  
  1323.  #define STR_NAME        101
  1324.  #define STR_TITLE       102
  1325.  #define STR_WHAT        103
  1326.  
  1327.  /* Menu command IDs */
  1328.  
  1329.  #define CMD_ABOUT       201
  1330.  #define CMD_EXIT        202
  1331.  #define CMD_WHAT        203
  1332.  
  1333.  /* Dialog box resource IDs */
  1334.  
  1335.  #define DLG_ABOUT       301
  1336.  #define DLG_WHAT        302
  1337.  
  1338.  /* 'What...' dialog box item IDs */
  1339.  
  1340.  #define ITEM_WHAT       401
  1341.  #define ITEM_INTBAR     402
  1342.  #define ITEM_INTERVAL   403
  1343.  
  1344.  #define ITEM_DISTBAR    404
  1345.  #define ITEM_DISTANCE   405
  1346.  #define ITEM_CLEAN      406
  1347.  #define ITEM_FLICKER    407
  1348.  
  1349.  /* Timer IDs */
  1350.  
  1351.  #define TIMER_MOVE      501
  1352.  #define TIMER_CHAR      502
  1353.  
  1354.  /* Menu resource IDs */
  1355.  
  1356.  #define MENU_WHAT       601
  1357.  
  1358.  
  1359.  Figure 6W:  SW.C is the C Source Code Listing for SAYWHAT
  1360.  
  1361.  /* SW.C
  1362.   * SW.C - C code for SayWhat - Windows version
  1363.   */
  1364.  
  1365.  /* Note - this code *must* be compiled with the -Gc switch */
  1366.  
  1367.  #ifndef LINT_ARGS
  1368.  #define LINT_ARGS  /* turn on argument checking for C runtime */
  1369.  #endif
  1370.  
  1371.  #include <windows.h>
  1372.  #include <limits.h>
  1373.  #include <stdlib.h>
  1374.  #include <string.h>
  1375.  #include <time.h>
  1376.  #include "sw.h"
  1377.  
  1378.  #define MIN_INTERVAL    1       /* limits for nInterval */
  1379.  #define MAX_INTERVAL    999
  1380.  
  1381.  #define MIN_DISTANCE    1       /* limits for nDistance */
  1382.  #define MAX_DISTANCE    99
  1383.  
  1384.  /*  Static variables  */
  1385.  
  1386.  HANDLE  hInstance;              /* application instance handle */
  1387.  
  1388.  HWND    hWndWhat;               /* main window handle */
  1389.  HWND    hWndPanel;              /* contral panel dialog handle */
  1390.  
  1391.  FARPROC lpfnDlgProc;            /* ProcInstance for dialog */
  1392.  
  1393.  char    szAppName[10];          /* window class name */
  1394.  char    szTitle[15];            /* main window title */
  1395.  
  1396.  char    szText[40];             /* current "what" text */
  1397.  NPSTR   pText = szText;         /* ptr into szText for icon mode */
  1398.  char    cBlank = ' ';           /* a blank we can point to */
  1399.  
  1400.  RECT    rcText;                 /* current text rectangle */
  1401.  POINT   ptAdvance;              /* increments for SayAdvanceText */
  1402.  POINT   ptCharSize;             /* X and Y size of a character */
  1403.  POINT   ptScreenSize;           /* X and Y size of the screen */
  1404.  
  1405.  int     nDisplayPlanes;         /* number of display planes */
  1406.  DWORD   rgbTextColor;           /* current text color */
  1407.  HBRUSH  hbrBkgd;                /* brush for erasing background */
  1408.  
  1409.  int     nInterval = 40;         /* current "Interval" setting */
  1410.  int     nDistance = 30;         /* current "Distance" setting */
  1411.  int     nDistLeft = 0;          /* change direction when hits 0 */
  1412.  BOOL    bCleanPaint = TRUE;     /* clean or flickery painting? */
  1413.  BOOL    bMouseDown = FALSE;     /* is mouse down? */
  1414.  BOOL    bIconic = FALSE;        /* is main window iconic? */
  1415.  
  1416.  /*  Full prototypes for our functions to get type checking  */
  1417.  
  1418.  BOOL FAR SayAboutDlgProc( HWND, unsigned, WORD, LONG );
  1419.  void     SayAdvanceTextChar( HWND );
  1420.  void     SayAdvanceTextPos( HWND );
  1421.  void     SayChangeColor( HWND );
  1422.  void     SayDoBarMsg( HWND, HWND, WORD, int );
  1423.  void     SayFillRect( HDC, int, int, int, int );
  1424.  void     SayInitBar( HWND, int, int, int, int );
  1425.  BOOL     SayInitApp( HANDLE, int );
  1426.  void     SayInvalidateText( HWND );
  1427.  void     SayLimitTextPos( HWND );
  1428.  void     SayMoveText( HWND, POINT );
  1429.  void     SaySetBar( HWND, int *, int );
  1430.  void     SayExitApp( int );
  1431.  BOOL FAR SayWhatDlgProc( HWND, unsigned, WORD, LONG );
  1432.  void     SayWhatPaint( HWND );
  1433.  LONG FAR SayWhatWndProc( HWND, unsigned, WORD, LONG );
  1434.  void     WinMain( HANDLE, HANDLE, LPSTR, int );
  1435.  
  1436.  /*  Dialog function for the "About" box  */
  1437.  
  1438.  BOOL FAR SayAboutDlgProc( hWndDlg, wMsg, wParam, lParam )
  1439.      HWND        hWndDlg;
  1440.      unsigned    wMsg;
  1441.      WORD        wParam;
  1442.      LONG        lParam;
  1443.  {
  1444.      switch( wMsg )
  1445.      {
  1446.        case WM_COMMAND:
  1447.          switch( wParam )
  1448.          {
  1449.            case IDOK:
  1450.              EndDialog( hWndDlg, TRUE );
  1451.              return TRUE;
  1452.          }
  1453.      }
  1454.      return FALSE;
  1455.  }
  1456.  
  1457.  /*  Advances to next display character in iconic mode.
  1458.   *  Forces in a blank when it reaches the end of string.
  1459.   */
  1460.  
  1461.  void SayAdvanceTextChar( hWnd )
  1462.      HWND        hWnd;
  1463.  {
  1464.      if( ! bIconic )
  1465.          return;
  1466.  
  1467.      if( pText = = &cBlank )
  1468.          pText = szText;
  1469.      else if( ! *(++pText) )
  1470.          pText = &cBlank;
  1471.  
  1472.      SayChangeColor( hWnd );
  1473.      SayInvalidateText( hWnd );
  1474.  }
  1475.  
  1476.  /*  Advances text position according to ptAdvance.  Decrements
  1477.   *  nDistLeft first, and when it reaches zero, sets a new
  1478.   *  randomized ptAdvance and nDistLeft, also changes color.
  1479.   *  Does nothing if mouse is down, so text will track mouse.
  1480.   */
  1481.  
  1482.  void SayAdvanceTextPos( hWnd )
  1483.      HWND        hWnd;
  1484.  {
  1485.      int         i;
  1486.  
  1487.      if( bMouseDown )
  1488.          return;
  1489.  
  1490.      SayInvalidateText( hWnd );
  1491.  
  1492.      if( nDistLeft- < 0 ) {
  1493.          nDistLeft = rand() % nDistance + 1;
  1494.          do {
  1495.              i = rand();
  1496.              ptAdvance.x = (
  1497.                  i < SHRT_MAX/3   ? -1
  1498.                : i < SHRT_MAX/3*2 ?  0
  1499.                :                     1
  1500.              );
  1501.              i = rand();
  1502.              ptAdvance.y = (
  1503.                  i < SHRT_MAX/3   ? -1
  1504.                : i < SHRT_MAX/3*2 ?  0
  1505.                :                     1
  1506.              );
  1507.          } while( ptAdvance.x = = 0  &&  ptAdvance.y = = 0 );
  1508.  
  1509.          if( ! bIconic )
  1510.              SayChangeColor( hWnd );
  1511.      } else {
  1512.          rcText.left   += ptAdvance.x;
  1513.          rcText.right  += ptAdvance.x;
  1514.          rcText.top    += ptAdvance.y;
  1515.          rcText.bottom += ptAdvance.y;
  1516.      }
  1517.  
  1518.      SayInvalidateText( hWnd );
  1519.  }
  1520.  
  1521.  /*  Changes color to a random selection, if color is available.
  1522.   *  Forces a color change - if the random selection is the same
  1523.   *  as the old one, it tries again.
  1524.   */
  1525.  
  1526.  void SayChangeColor( hWnd )
  1527.      HWND        hWnd;
  1528.  {
  1529.      HDC         hDC;
  1530.      DWORD       rgbNew;
  1531.      DWORD       rgbWindow;
  1532.  
  1533.      if( nDisplayPlanes <= 1 ) {
  1534.          rgbTextColor = GetSysColor(COLOR_WINDOWTEXT);
  1535.      } else {
  1536.          rgbWindow = GetSysColor(COLOR_WINDOW);
  1537.          hDC = GetDC( hWnd );
  1538.          do {
  1539.              rgbNew = GetNearestColor(
  1540.                  hDC,
  1541.                  MAKELONG( rand(), rand() ) & 0x00FFFFFFL
  1542.              );
  1543.          } while( rgbNew = = rgbWindow || rgbNew = = rgbTextColor );
  1544.          rgbTextColor = rgbNew;
  1545.          ReleaseDC( hWnd, hDC );
  1546.      }
  1547.  }
  1548.  
  1549.  /*  Handles scroll bar messages from the control dialog box.
  1550.   *  Adjusts scroll bar position, taking its limits into account,
  1551.   *  copies the scroll bar value into the adjacent edit control,
  1552.   *  then sets the nDistance or nInterval variable appropriately.
  1553.   */
  1554.  
  1555.  void SayDoBarMsg( hWndDlg, hWndBar, wCode, nThumb )
  1556.      HWND        hWndDlg;
  1557.      HWND        hWndBar;
  1558.      WORD        wCode;
  1559.      int         nThumb;
  1560.  {
  1561.      int         nPos;
  1562.      int         nOldPos;
  1563.      int         nMin;
  1564.      int         nMax;
  1565.      int         idBar;
  1566.  
  1567.      idBar = GetWindowWord( hWndBar, GWW_ID );
  1568.  
  1569.      nOldPos = nPos = GetScrollPos( hWndBar, SB_CTL );
  1570.      GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );
  1571.  
  1572.      switch( wCode )
  1573.      {
  1574.          case SB_LINEUP:         -nPos;              break;
  1575.  
  1576.          case SB_LINEDOWN:       ++nPos;             break;
  1577.  
  1578.          case SB_PAGEUP:         nPos -= 10;         break;
  1579.  
  1580.          case SB_PAGEDOWN:       nPos += 10;         break;
  1581.  
  1582.          case SB_THUMBPOSITION:
  1583.          case SB_THUMBTRACK:     nPos = nThumb;      break;
  1584.  
  1585.          case SB_TOP:            nPos = nMin;        break;
  1586.  
  1587.          case SB_BOTTOM:         nPos = nMax;        break;
  1588.      }
  1589.  
  1590.      if( nPos < nMin )
  1591.          nPos = nMin;
  1592.  
  1593.      if( nPos > nMax )
  1594.          nPos = nMax;
  1595.  
  1596.      if( nPos = = nOldPos )
  1597.          return;
  1598.  
  1599.      SetScrollPos( hWndBar, SB_CTL, nPos, TRUE );
  1600.  
  1601.      SetDlgItemInt( hWndDlg, idBar+1, nPos, FALSE );
  1602.  
  1603.      switch( idBar )
  1604.      {
  1605.        case ITEM_DISTBAR:
  1606.          nDistance = nPos;
  1607.          break;
  1608.  
  1609.        case ITEM_INTBAR:
  1610.          KillTimer( hWndWhat, TIMER_MOVE );
  1611.          nInterval = nPos;
  1612.          SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
  1613.          InvalidateRect( hWndWhat, NULL, FALSE );
  1614.          break;
  1615.      }
  1616.  }
  1617.  
  1618.  /*  Terminates the application, freeing up allocated resources.
  1619.   *  Note that this function does NOT return to the caller, but
  1620.   *  exits the program.
  1621.   */
  1622.  
  1623.  void SayExitApp( nRet )
  1624.      int         nRet;
  1625.  {
  1626.      if( GetModuleUsage(hInstance) = = 1 ) {
  1627.          DeleteObject( hbrBkgd );
  1628.      }
  1629.  
  1630.      exit( nRet );
  1631.  }
  1632.  
  1633.  /*  Fills a specified rectangle with the background color.
  1634.   *  Checks that the rectangle is non-empty first.
  1635.   */
  1636.  
  1637.  void SayFillRect( hDC, nLeft, nTop, nRight, nBottom )
  1638.      HDC         hDC;
  1639.      int         nLeft;
  1640.      int         nTop;
  1641.      int         nRight;
  1642.      int         nBottom;
  1643.  
  1644.  {
  1645.      RECT        rcFill;
  1646.  
  1647.      if( nLeft >= nRight  ||  nTop >= nBottom )
  1648.          return;
  1649.  
  1650.      SetRect( &rcFill, nLeft, nTop, nRight, nBottom );
  1651.  
  1652.      FillRect( hDC, &rcFill, hbrBkgd );
  1653.  }
  1654.  
  1655.  /*  Initializes the application.  */
  1656.  
  1657.  BOOL SayInitApp( hPrevInstance, nCmdShow )
  1658.  
  1659.      HANDLE      hPrevInstance;
  1660.      int         nCmdShow;
  1661.  {
  1662.      WNDCLASS    Class;
  1663.      HDC         hDC;
  1664.      TEXTMETRIC  Metrics;
  1665.  
  1666.      LoadString(
  1667.          hInstance, STR_NAME,  szAppName, sizeof(szAppName)
  1668.      );
  1669.      LoadString(
  1670.          hInstance, STR_TITLE, szTitle,   sizeof(szTitle)
  1671.      );
  1672.      LoadString(
  1673.          hInstance, STR_WHAT,  szText,    sizeof(szText)
  1674.      );
  1675.  
  1676.      hDC = CreateIC( "DISPLAY", NULL, NULL, NULL );
  1677.      GetTextMetrics( hDC, &Metrics );
  1678.      nDisplayPlanes = GetDeviceCaps( hDC, PLANES );
  1679.      DeleteDC( hDC );
  1680.  
  1681.      ptCharSize.x = Metrics.tmMaxCharWidth;
  1682.      ptCharSize.y = Metrics.tmHeight;
  1683.  
  1684.      ptScreenSize.x = GetSystemMetrics(SM_CXSCREEN);
  1685.      ptScreenSize.y = GetSystemMetrics(SM_CYSCREEN);
  1686.  
  1687.      if( ! hPrevInstance ) {
  1688.  
  1689.          hbrBkgd = CreateSolidBrush( GetSysColor(COLOR_WINDOW) );
  1690.  
  1691.          Class.style         = 0; /* CS_HREDRAW | CS_VREDRAW; */
  1692.          Class.lpfnWndProc   = SayWhatWndProc;
  1693.          Class.cbClsExtra    = 0;
  1694.          Class.cbWndExtra    = 0;
  1695.          Class.hInstance     = hInstance;
  1696.          Class.hIcon         = NULL;
  1697.          Class.hCursor       = LoadCursor( NULL, IDC_ARROW );
  1698.          Class.hbrBackground = COLOR_WINDOW + 1;
  1699.          Class.lpszMenuName  = szAppName;
  1700.          Class.lpszClassName = szAppName;
  1701.  
  1702.          if( ! RegisterClass( &Class ) )
  1703.              return FALSE;
  1704.  
  1705.      } else {
  1706.  
  1707.          GetInstanceData(
  1708.              hPrevInstance, (NPSTR)&hbrBkgd, sizeof(hbrBkgd)
  1709.          );
  1710.      }
  1711.  
  1712.      hWndWhat = CreateWindow(
  1713.          szAppName,
  1714.          szTitle,
  1715.          WS_OVERLAPPEDWINDOW,
  1716.          CW_USEDEFAULT, 0,
  1717.          CW_USEDEFAULT, 0,
  1718.          (HWND)NULL,
  1719.          (HMENU)NULL,
  1720.          hInstance,
  1721.          (LPSTR)NULL
  1722.      );
  1723.  
  1724.      if( ! hWndWhat )
  1725.          return FALSE;
  1726.  
  1727.      ShowWindow( hWndWhat, nCmdShow );
  1728.      UpdateWindow( hWndWhat );
  1729.  
  1730.      return TRUE;
  1731.  }
  1732.  
  1733.  /*  Initializes one scroll bar in the control dialog.  */
  1734.  
  1735.  void SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
  1736.      HWND        hWndDlg;
  1737.      int         idBar;
  1738.      int         nValue;
  1739.  
  1740.      int         nMin;
  1741.      int         nMax;
  1742.  {
  1743.      HWND        hWndBar;
  1744.  
  1745.      hWndBar = GetDlgItem( hWndDlg, idBar );
  1746.  
  1747.      SetScrollRange( hWndBar, SB_CTL, nMin, nMax, FALSE );
  1748.      SetScrollPos( hWndBar, SB_CTL, nValue, FALSE );
  1749.  
  1750.      SetDlgItemInt( hWndDlg, idBar+1, nValue, FALSE );
  1751.  }
  1752.  
  1753.  /*  Invalidates the text within the main window, adjusting the
  1754.   *  text rectangle if it's gone out of bounds.
  1755.   */
  1756.  
  1757.  void SayInvalidateText( hWnd )
  1758.      HWND        hWnd;
  1759.  {
  1760.      SayLimitTextPos( hWnd );
  1761.      InvalidateRect( hWnd, &rcText, FALSE );
  1762.  }
  1763.  
  1764.  /*  Checks the text position against the window client area
  1765.   *  rectangle.  If it's moved off the window in any direction,
  1766.   *  forces it back inside, and also reverses the ptAdvance value
  1767.   *  for that direction so it will "bounce" off the edge.  Handles
  1768.   *  both the iconic and open window cases.
  1769.   */
  1770.  
  1771.  void SayLimitTextPos( hWnd )
  1772.      HWND        hWnd;
  1773.  {
  1774.      RECT        rcClient;
  1775.      POINT       ptTextSize;
  1776.  
  1777.      ptTextSize = ptCharSize;
  1778.  
  1779.      if( ! bIconic ) {
  1780.          pText = szText;
  1781.          ptTextSize.x *= strlen(szText);
  1782.      }
  1783.  
  1784.      GetClientRect( hWndWhat, &rcClient );
  1785.  
  1786.      if( rcText.left > rcClient.right - ptTextSize.x ) {
  1787.          rcText.left = rcClient.right - ptTextSize.x;
  1788.          ptAdvance.x = -ptAdvance.x;
  1789.      }
  1790.  
  1791.      if( rcText.left < rcClient.left ) {
  1792.          rcText.left = rcClient.left;
  1793.          ptAdvance.x = -ptAdvance.x;
  1794.      }
  1795.  
  1796.      if( rcText.top > rcClient.bottom - ptTextSize.y ) {
  1797.          rcText.top = rcClient.bottom - ptTextSize.y;
  1798.          ptAdvance.y = -ptAdvance.y;
  1799.      }
  1800.  
  1801.  
  1802.      if( rcText.top < rcClient.top ) {
  1803.          rcText.top = rcClient.top;
  1804.          ptAdvance.y = -ptAdvance.y;
  1805.      }
  1806.  
  1807.      rcText.right  = rcText.left + ptTextSize.x;
  1808.      rcText.bottom = rcText.top  + ptTextSize.y;
  1809.  }
  1810.  
  1811.  /*  Moves the text within the window, by invalidating the old
  1812.   *  position, adjusting rcText according to ptMove, and then
  1813.   *  invalidating the new position.
  1814.   */
  1815.  
  1816.  void SayMoveText( hWnd, ptMove )
  1817.      HWND        hWnd;
  1818.      POINT       ptMove;
  1819.  {
  1820.      SayInvalidateText( hWnd );
  1821.      rcText.left = ptMove.x - (rcText.right  - rcText.left >> 1);
  1822.      rcText.top  = ptMove.y - (rcText.bottom - rcText.top  >> 1);
  1823.      SayInvalidateText( hWnd );
  1824.  }
  1825.  
  1826.  /*  Sets one of the dialog scroll bars to *pnValue.  If that
  1827.   *  value is out of range, limits it to the proper range and
  1828.   *  forces *pnValue to be within the range as well.
  1829.   */
  1830.  
  1831.  void SaySetBar( hWndDlg, pnValue, idBar )
  1832.      HWND        hWndDlg;
  1833.      int *       pnValue;
  1834.      int         idBar;
  1835.  {
  1836.      HWND        hWndBar;
  1837.      int         nMin;
  1838.      int         nMax;
  1839.      int         nValue;
  1840.      BOOL        bOK;
  1841.  
  1842.      hWndBar = GetDlgItem( hWndDlg, idBar );
  1843.  
  1844.      GetScrollRange( hWndBar, SB_CTL, &nMin, &nMax );
  1845.  
  1846.      nValue = GetDlgItemInt( hWndDlg, idBar+1, &bOK, FALSE );
  1847.  
  1848.      if( bOK  &&  nValue >= nMin  &&  nValue <= nMax ) {
  1849.          *pnValue = nValue;
  1850.          SetScrollPos( hWndBar, SB_CTL, nValue, TRUE );
  1851.      } else {
  1852.          SetDlgItemInt(
  1853.              hWndDlg,
  1854.              idBar+1,
  1855.              GetScrollPos( hWndBar, SB_CTL ),
  1856.              FALSE
  1857.          );
  1858.      }
  1859.  }
  1860.  
  1861.  /*  Dialog function for the control panel dialog box.  */
  1862.  
  1863.  BOOL FAR SayWhatDlgProc( hWndDlg, wMsg, wParam, lParam )
  1864.      HWND        hWndDlg;
  1865.      unsigned    wMsg;
  1866.      WORD        wParam;
  1867.      LONG        lParam;
  1868.  {
  1869.      HWND        hWndBar;
  1870.      RECT        rcWin;
  1871.      int         n;
  1872.  
  1873.      switch( wMsg )
  1874.      {
  1875.        case WM_COMMAND:
  1876.          switch( wParam )
  1877.          {
  1878.            case IDOK:
  1879.              KillTimer( hWndWhat, TIMER_MOVE );
  1880.              GetDlgItemText(
  1881.                  hWndDlg, ITEM_WHAT, szText, sizeof(szText)
  1882.              );
  1883.              if( strlen(szText) = = 0 )
  1884.                  LoadString(
  1885.                      hInstance, STR_WHAT, szText, sizeof(szText)
  1886.                  );
  1887.              pText = szText;
  1888.              SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
  1889.              SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
  1890.              SetTimer( hWndWhat, TIMER_MOVE, nInterval, NULL );
  1891.              InvalidateRect( hWndWhat, NULL, FALSE );
  1892.              return TRUE;
  1893.  
  1894.            case IDCANCEL:
  1895.              DestroyWindow( hWndDlg );
  1896.              return TRUE;
  1897.  
  1898.            case ITEM_CLEAN:
  1899.            case ITEM_FLICKER:
  1900.              bCleanPaint =
  1901.                  IsDlgButtonChecked( hWndDlg, ITEM_CLEAN );
  1902.              return TRUE;
  1903.          }
  1904.          return FALSE;
  1905.  
  1906.        case WM_HSCROLL:
  1907.          if( HIWORD(lParam) )
  1908.              SayDoBarMsg(
  1909.                  hWndDlg, HIWORD(lParam), wParam, LOWORD(lParam)
  1910.              );
  1911.          return TRUE;
  1912.  
  1913.        case WM_INITDIALOG:
  1914.          GetWindowRect( hWndDlg, &rcWin );
  1915.          ClientToScreen( hWndWhat, (LPPOINT)&rcWin.left );
  1916.          ClientToScreen( hWndWhat, (LPPOINT)&rcWin.right );
  1917.          n = rcWin.right - ptScreenSize.x + ptCharSize.x;
  1918.          if( n > 0 )
  1919.              rcWin.left -= n;
  1920.          rcWin.left &= ~7;  /* byte align */
  1921.          n = rcWin.bottom - ptScreenSize.y + ptCharSize.y;
  1922.          if( n > 0 )
  1923.              rcWin.top -= n;
  1924.          SetWindowPos(
  1925.              hWndDlg,
  1926.              (HWND)NULL,
  1927.              rcWin.left,
  1928.              rcWin.top,
  1929.              0, 0,
  1930.              SWP_NOSIZE | SWP_NOZORDER |
  1931.                  SWP_NOREDRAW | SWP_NOACTIVATE
  1932.          );
  1933.          SetDlgItemText( hWndDlg, ITEM_WHAT, szText );
  1934.          SendDlgItemMessage(
  1935.              hWndDlg, ITEM_WHAT,
  1936.              EM_LIMITTEXT, sizeof(szText)-1, 0L
  1937.          );
  1938.          SayInitBar(
  1939.              hWndDlg, ITEM_INTBAR,
  1940.              nInterval, MIN_INTERVAL, MAX_INTERVAL
  1941.          );
  1942.  
  1943.          SayInitBar(
  1944.              hWndDlg, ITEM_DISTBAR,
  1945.              nDistance, MIN_DISTANCE, MAX_DISTANCE
  1946.          );
  1947.          CheckDlgButton( hWndDlg, ITEM_CLEAN, TRUE );
  1948.          return TRUE;
  1949.  
  1950.        case WM_NCDESTROY:
  1951.          FreeProcInstance( lpfnDlgProc );
  1952.          hWndPanel = NULL;
  1953.          return FALSE;
  1954.      }
  1955.      return FALSE;
  1956.  }
  1957.  
  1958.  /*  Painting procedure for the main window.  Handles both the
  1959.   *  clean and flickery painting methods for demonstration
  1960.   *  purposes.
  1961.   */
  1962.  
  1963.  void SayWhatPaint( hWnd )
  1964.      HWND        hWnd;
  1965.  {
  1966.      PAINTSTRUCT ps;
  1967.  
  1968.      BeginPaint( hWnd, &ps );
  1969.  
  1970.      SetTextColor( ps.hdc, rgbTextColor );
  1971.  
  1972.      SayLimitTextPos( hWnd );
  1973.  
  1974.      if( bCleanPaint ) {
  1975.  
  1976.          /* Clean painting, avoid redundant erasing */
  1977.  
  1978.          TextOut(
  1979.              ps.hdc,
  1980.              rcText.left,
  1981.              rcText.top,
  1982.              pText,
  1983.              bIconic ? 1 : strlen(szText)
  1984.          );
  1985.  
  1986.          SayFillRect(
  1987.              ps.hdc,
  1988.              ps.rcPaint.left,
  1989.              ps.rcPaint.top,
  1990.              rcText.left,
  1991.              ps.rcPaint.bottom
  1992.          );
  1993.  
  1994.          SayFillRect(
  1995.              ps.hdc,
  1996.              rcText.left,
  1997.              ps.rcPaint.top,
  1998.              rcText.right,
  1999.              rcText.top
  2000.          );
  2001.  
  2002.          SayFillRect(
  2003.              ps.hdc,
  2004.              rcText.left,
  2005.              rcText.bottom,
  2006.              rcText.right,
  2007.              ps.rcPaint.bottom
  2008.          );
  2009.  
  2010.          SayFillRect(
  2011.              ps.hdc,
  2012.              rcText.right,
  2013.              ps.rcPaint.top,
  2014.              ps.rcPaint.right,
  2015.              ps.rcPaint.bottom
  2016.          );
  2017.  
  2018.      } else {
  2019.  
  2020.          /* Flickery painting, erase background and paint traditionally */
  2021.  
  2022.          FillRect( ps.hdc, &ps.rcPaint, hbrBkgd );
  2023.  
  2024.          TextOut(
  2025.              ps.hdc,
  2026.              rcText.left,
  2027.              rcText.top,
  2028.              pText,
  2029.              bIconic ? 1 : strlen(szText)
  2030.          );
  2031.  
  2032.      }
  2033.  
  2034.      EndPaint( hWnd, &ps );
  2035.  
  2036.      if( ! nInterval )
  2037.          SayAdvanceTextPos( hWnd );
  2038.  }
  2039.  /*  Window function for the main window.  */
  2040.  
  2041.  LONG FAR SayWhatWndProc( hWnd, wMsg, wParam, lParam )
  2042.      HWND        hWnd;
  2043.      unsigned    wMsg;
  2044.      WORD        wParam;
  2045.      LONG        lParam;
  2046.  {
  2047.      FARPROC     lpfnAbout;
  2048.  
  2049.      switch( wMsg )
  2050.      {
  2051.        case WM_COMMAND:
  2052.          switch( wParam )
  2053.          {
  2054.            case CMD_ABOUT:
  2055.              lpfnAbout =
  2056.                  MakeProcInstance( SayAboutDlgProc, hInstance );
  2057.              DialogBox(
  2058.                  hInstance, MAKEINTRESOURCE(DLG_ABOUT),
  2059.                  hWnd, lpfnAbout
  2060.              );
  2061.              FreeProcInstance( lpfnAbout );
  2062.              return 0L;
  2063.  
  2064.            case CMD_EXIT:
  2065.              DestroyWindow( hWndWhat );
  2066.              return 0L;
  2067.  
  2068.            case CMD_WHAT:
  2069.              if( hWndPanel ) {
  2070.                  BringWindowToTop( hWndPanel );
  2071.              } else {
  2072.                  lpfnDlgProc =
  2073.                      MakeProcInstance( SayWhatDlgProc, hInstance );
  2074.                  if( ! lpfnDlgProc )
  2075.                      return 0L;
  2076.                  hWndPanel = CreateDialog(
  2077.                      hInstance,
  2078.                      MAKEINTRESOURCE(DLG_WHAT),
  2079.                      (HWND)NULL,
  2080.                      lpfnDlgProc
  2081.                  );
  2082.                  if( ! hWndPanel )
  2083.                      FreeProcInstance( lpfnDlgProc );
  2084.              }
  2085.          }
  2086.          break;
  2087.  
  2088.        case WM_CREATE:
  2089.          srand( (int)time(NULL) );
  2090.          SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
  2091.          return 0L;
  2092.  
  2093.        case WM_DESTROY:
  2094.          if( hWndPanel )
  2095.              DestroyWindow( hWndPanel );
  2096.          PostQuitMessage( 0 );
  2097.          return 0L;
  2098.  
  2099.        case WM_KEYDOWN:
  2100.          SayInvalidateText( hWnd );
  2101.          switch( wParam )
  2102.          {
  2103.            case VK_LEFT:
  2104.              rcText.left -= ptCharSize.x;
  2105.              ptAdvance.x  = -1;
  2106.              ptAdvance.y  = 0;
  2107.              break;
  2108.  
  2109.            case VK_RIGHT:
  2110.              rcText.left += ptCharSize.x;
  2111.              ptAdvance.x  = 1;
  2112.              ptAdvance.y  = 0;
  2113.              break;
  2114.  
  2115.            case VK_UP:
  2116.              rcText.top  -= ptCharSize.y >> 1;
  2117.              ptAdvance.x  = 0;
  2118.              ptAdvance.y  = -1;
  2119.              break;
  2120.  
  2121.            case VK_DOWN:
  2122.              rcText.top  += ptCharSize.y >> 1;
  2123.              ptAdvance.x  = 0;
  2124.              ptAdvance.y  = 1;
  2125.              break;
  2126.  
  2127.            default:
  2128.              return 0L;
  2129.          }
  2130.          SayInvalidateText( hWnd );
  2131.          nDistLeft = nDistance;
  2132.          return 0L;
  2133.  
  2134.        case WM_LBUTTONDOWN:
  2135.          if( bMouseDown )
  2136.              break;
  2137.          KillTimer( hWnd, TIMER_MOVE );
  2138.          bMouseDown = TRUE;
  2139.          SetCapture( hWnd );
  2140.          SayMoveText( hWnd, MAKEPOINT(lParam) );
  2141.          break;
  2142.  
  2143.        case WM_LBUTTONUP:
  2144.          if( ! bMouseDown )
  2145.              break;
  2146.          bMouseDown = FALSE;
  2147.          ReleaseCapture();
  2148.          SayMoveText( hWnd, MAKEPOINT(lParam) );
  2149.          SetTimer( hWnd, TIMER_MOVE, nInterval, NULL );
  2150.          break;
  2151.  
  2152.        case WM_MOUSEMOVE:
  2153.          if( bMouseDown )
  2154.              SayMoveText( hWnd, MAKEPOINT(lParam) );
  2155.          break;
  2156.  
  2157.        case WM_PAINT:
  2158.          SayWhatPaint( hWnd );
  2159.          return 0L;
  2160.  
  2161.        case WM_SIZE:
  2162.          if( wParam = = SIZEICONIC ) {
  2163.              if( ! bIconic )
  2164.                  SetTimer( hWnd, TIMER_CHAR, 1000, NULL );
  2165.              bIconic = TRUE;
  2166.          } else {
  2167.              if( bIconic )
  2168.                  KillTimer( hWnd, TIMER_CHAR );
  2169.              bIconic = FALSE;
  2170.          }
  2171.          SayInvalidateText( hWnd );
  2172.          nDistLeft = 0;
  2173.          SayAdvanceTextPos( hWnd );
  2174.          return 0L;
  2175.  
  2176.        case WM_TIMER:
  2177.          switch( wParam )
  2178.          {
  2179.            case TIMER_MOVE:
  2180.              SayAdvanceTextPos( hWnd );
  2181.              break;
  2182.  
  2183.            case TIMER_CHAR:
  2184.              SayAdvanceTextChar( hWnd );
  2185.              break;
  2186.          }
  2187.          return 0L;
  2188.      }
  2189.      return DefWindowProc( hWnd, wMsg, wParam, lParam );
  2190.  }
  2191.  
  2192.  /*  Main function for the application.  */
  2193.  
  2194.  void WinMain( hInst, hPrevInst, lpszCmdLine, nCmdShow )
  2195.      HANDLE      hInst;
  2196.      HANDLE      hPrevInst;
  2197.      LPSTR       lpszCmdLine;
  2198.      int         nCmdShow;
  2199.  {
  2200.      MSG         msg;
  2201.  
  2202.      hInstance = hInst;
  2203.  
  2204.      if( ! SayInitApp( hPrevInst, nCmdShow ) )
  2205.          SayExitApp( 1 );
  2206.  
  2207.      while( GetMessage( &msg, NULL, 0, 0 ) ) {
  2208.  
  2209.          if( hWndPanel  &&  IsDialogMessage( hWndPanel, &msg ) )
  2210.              continue;
  2211.  
  2212.          TranslateMessage( &msg );
  2213.          DispatchMessage( &msg );
  2214.      }
  2215.  
  2216.      SayExitApp( msg.wParam );
  2217.  }
  2218.  
  2219.  
  2220.  Figure 6PM:  SWP.C is the C Source Code Listing for SAYWHAT
  2221.  
  2222.  /*
  2223.   *  SWP.C - C code for SayWhat - Presentation Manager version
  2224.   */
  2225.  
  2226.  #ifndef LINT_ARGS
  2227.  #define LINT_ARGS  /* turn on argument checking for C runtime */
  2228.  #endif
  2229.  
  2230.  #include <os2pm.h>
  2231.  #include <limits.h>
  2232.  #include <stdlib.h>
  2233.  #include <string.h>
  2234.  #include <time.h>
  2235.  #include ╙swp.h╙
  2236.  
  2237.  #define MIN_INTERVAL    1       /* limits for nInterval */
  2238.  #define MAX_INTERVAL    999
  2239.  
  2240.  #define MIN_DISTANCE    1       /* limits for nDistance */
  2241.  #define MAX_DISTANCE    99
  2242.  
  2243.  #define COLORDATAMAX    5
  2244.  
  2245.  /*  Static variables  */
  2246.  
  2247.  HAB     hAB = NULL;             /* anchor block handle */
  2248.  HMQ     hMsgQ = NULL;           /* message queue handle */
  2249.  
  2250.  HWND    hWndWhatFrame;          /* frame window handle */
  2251.  HWND    hWndWhat;               /* client window handle */
  2252.  HWND    hWndPanel;              /* control panel dialog handle */
  2253.  
  2254.  CHAR    szAppName[10];          /* window class name */
  2255.  CHAR    szTitle[15];            /* main window title */
  2256.  
  2257.  CHAR    szText[40];             /* current 'what' text */
  2258.  PSZ     npszText = szText;      /* ptr into szText for icon mode */
  2259.  CHAR    cBlank = ╒ ╒;           /* a blank we can point to */
  2260.  
  2261.  RECT    rcText;                 /* current text rectangle */
  2262.  POINT   ptAdvance;              /* increments for SayAdvanceText */
  2263.  POINT   ptCharSize;             /* X and Y size of a character */
  2264.  POINT   ptScreenSize;           /* X and Y size of the screen */
  2265.  
  2266.  LONG    lColorMax;              /* number of available colors */
  2267.  LONG    lColor;                 /* current text color index */
  2268.  
  2269.  SHORT   nInterval = 40;         /* current 'Interval' setting */
  2270.  SHORT   nDistance = 30;         /* current 'Distance' setting */
  2271.  SHORT   nDistLeft = 0;          /* change direction when hits 0 */
  2272.  BOOL    bCleanPaint = TRUE;     /* clean or flickery painting? */
  2273.  BOOL    bMouseDown = FALSE;     /* is mouse down? */
  2274.  BOOL    bIconic = FALSE;        /* is main window iconic? */
  2275.  
  2276.  /*  Full prototypes for our functions to get type checking  */
  2277.  
  2278.  ULONG FAR PASCAL SayAboutDlgProc( HWND, USHORT, ULONG, ULONG );
  2279.  VOID             SayAdvanceTextChar( HWND );
  2280.  VOID             SayAdvanceTextPos( HWND );
  2281.  VOID             SayChangeColor( HWND );
  2282.  VOID             SayDoBarMsg( HWND, USHORT, USHORT, SHORT );
  2283.  VOID             SayExitApp( INT );
  2284.  VOID             SayFillRect( HPS, SHORT, SHORT, SHORT, SHORT );
  2285.  VOID             SayInitBar( HWND, SHORT, SHORT, SHORT, SHORT );
  2286.  BOOL             SayInitApp( VOID );
  2287.  VOID             SayInvalidateText( HWND );
  2288.  VOID             SayLimitTextPos( HWND );
  2289.  VOID             SayMoveText( HWND, POINT );
  2290.  VOID             SaySetBar( HWND, SHORT *, SHORT );
  2291.  ULONG FAR PASCAL SayWhatDlgProc( HWND, USHORT, ULONG, ULONG );
  2292.  VOID             SayWhatPaint( HWND );
  2293.  ULONG FAR PASCAL SayWhatWndProc( HWND, USHORT, ULONG, ULONG );
  2294.  void  cdecl      main( INT, PSZ );
  2295.  
  2296.  /*  Dialog function for the 'About' box  */
  2297.  
  2298.  ULONG FAR PASCAL SayAboutDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
  2299.      HWND        hWndDlg;
  2300.      USHORT      wMsg;
  2301.      ULONG       lParam1;
  2302.      ULONG       lParam2;
  2303.  {
  2304.      switch( wMsg )
  2305.      {
  2306.        case WM_COMMAND:
  2307.          switch( LOUSHORT(lParam1) )
  2308.          {
  2309.            case IDOK:
  2310.              WinDismissDlg( hWndDlg, TRUE );
  2311.              break;
  2312.          }
  2313.      }
  2314.      return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
  2315.  }
  2316.  
  2317.  /*  Advances to next display character in iconic mode.
  2318.   *  Forces in a blank when it reaches the end of string.
  2319.   */
  2320.  
  2321.  VOID SayAdvanceTextChar( hWnd )
  2322.      HWND        hWnd;
  2323.  {
  2324.      if( ! bIconic )
  2325.          return;
  2326.  
  2327.      if( npszText = = &cBlank )
  2328.          npszText = szText;
  2329.      else if( ! *(++npszText) )
  2330.          npszText = &cBlank;
  2331.  
  2332.      SayChangeColor( hWnd );
  2333.      SayInvalidateText( hWnd );
  2334.  }
  2335.  
  2336.  /*  Advances text position according to ptAdvance. Decrements
  2337.   *  nDistLeft first, and when it reaches zero, sets a new
  2338.   *  randomized ptAdvance and nDistLeft, also changes color.
  2339.   *  Does nothing if mouse is down, so text will track mouse.
  2340.   */
  2341.  
  2342.  VOID SayAdvanceTextPos( hWnd )
  2343.      HWND        hWnd;
  2344.  {
  2345.      SHORT       i;
  2346.  
  2347.      if( bMouseDown )
  2348.          return;
  2349.  
  2350.      SayInvalidateText( hWnd );
  2351.  
  2352.      if( nDistLeft- < 0 ) {
  2353.          nDistLeft = rand() % nDistance + 1;
  2354.          do {
  2355.              i = rand();
  2356.              ptAdvance.x = (
  2357.                  i < SHRT_MAX/3   ? -1
  2358.  
  2359.                : i < SHRT_MAX/3*2 ?  0
  2360.                :                     1
  2361.              );
  2362.              i = rand();
  2363.              ptAdvance.y = (
  2364.                  i < SHRT_MAX/3   ? -1
  2365.                : i < SHRT_MAX/3*2 ?  0
  2366.                :                     1
  2367.              );
  2368.          } while( ptAdvance.x = = 0  &&  ptAdvance.y = = 0 );
  2369.          if( ! bIconic )
  2370.              SayChangeColor( hWnd );
  2371.      } else {
  2372.          rcText.xLeft   += ptAdvance.x;
  2373.          rcText.xRight  += ptAdvance.x;
  2374.          rcText.yTop    += ptAdvance.y;
  2375.          rcText.yBottom += ptAdvance.y;
  2376.      }
  2377.  
  2378.      SayInvalidateText( hWnd );
  2379.  }
  2380.  
  2381.  /*  Changes color to a random selection, if color is available.
  2382.   *  Forces a color change - if the random selection is the same
  2383.   *  as the old one, it tries again.
  2384.   */
  2385.  
  2386.  VOID SayChangeColor( hWnd )
  2387.      HWND        hWnd;
  2388.  {
  2389.      HPS         hPS;
  2390.      LONG        lWindow;
  2391.      LONG        lNew;
  2392.  
  2393.      hPS = WinGetPS( hWnd );
  2394.  
  2395.      if( lColorMax <= 2 ) {
  2396.          lColor = GpiQueryColorIndex(
  2397.              hPS,
  2398.              WinQuerySysColor( hAB, SCLR_WINDOWTEXT ),
  2399.              0L
  2400.          );
  2401.  
  2402.      } else {
  2403.          lWindow = GpiQueryColorIndex(
  2404.              hPS,
  2405.              WinQuerySysColor( hAB, SCLR_WINDOW ),
  2406.              0L
  2407.          );
  2408.          do {
  2409.              lNew = rand() % lColorMax;
  2410.          } while( lNew = = lWindow || lNew = = lColor );
  2411.          lColor = lNew;
  2412.      }
  2413.  
  2414.      WinReleasePS( hPS );
  2415.  
  2416.  }
  2417.  
  2418.  /*  Handles scroll bar messages from the control dialog box.
  2419.   *  Adjusts scroll bar position, taking its limits into account,
  2420.   *  copies the scroll bar value into the adjacent edit control,
  2421.   *  then sets the nDistance or nInterval variable appropriately.
  2422.   */
  2423.  
  2424.  VOID SayDoBarMsg( hWndDlg, idBar, wCode, nThumb )
  2425.      HWND        hWndDlg;
  2426.      USHORT      idBar;
  2427.      USHORT      wCode;
  2428.      SHORT       nThumb;
  2429.  {
  2430.      SHORT       nPos;
  2431.      SHORT       nOldPos;
  2432.      ULONG       lRange;
  2433.  
  2434.      nOldPos = nPos =
  2435.          (SHORT)WinSendDlgItemMsg(
  2436.              hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
  2437.          );
  2438.  
  2439.      lRange =
  2440.          WinSendDlgItemMsg(
  2441.              hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
  2442.          );
  2443.  
  2444.      switch( wCode ) {
  2445.  
  2446.          case SB_LINEUP:         -nPos;                    break;
  2447.  
  2448.          case SB_LINEDOWN:       ++nPos;                   break;
  2449.  
  2450.          case SB_PAGEUP:         nPos -= 10;               break;
  2451.  
  2452.          case SB_PAGEDOWN:       nPos += 10;               break;
  2453.  
  2454.          case SB_THUMBPOSITION:
  2455.          case SB_THUMBTRACK:     nPos = nThumb;            break;
  2456.  
  2457.          case SB_TOP:            nPos = LOUSHORT(lRange);  break;
  2458.  
  2459.          case SB_BOTTOM:         nPos = HIUSHORT(lRange);  break;
  2460.  
  2461.      }
  2462.  
  2463.      if( nPos < LOUSHORT(lRange) )
  2464.          nPos = LOUSHORT(lRange);
  2465.  
  2466.      if( nPos > HIUSHORT(lRange) )
  2467.          nPos = HIUSHORT(lRange);
  2468.  
  2469.      if( nPos = = nOldPos )
  2470.          return;
  2471.  
  2472.      WinSendDlgItemMsg(
  2473.          hWndDlg, idBar, SBM_SETPOS, (LONG)nPos, 0L
  2474.      );
  2475.  
  2476.      WinSetDlgItemShort( hWndDlg, idBar+1, nPos, FALSE );
  2477.  
  2478.      switch( idBar )
  2479.      {
  2480.        case ITEM_DISTBAR:
  2481.          nDistance = nPos;
  2482.          break;
  2483.  
  2484.        case ITEM_INTBAR:
  2485.          WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
  2486.          nInterval = nPos;
  2487.          WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );
  2488.  
  2489.          WinInvalidateRect( hWndWhat, NULL );
  2490.          break;
  2491.      }
  2492.  }
  2493.  
  2494.  /*  Terminates the application, freeing up allocated resources.
  2495.   *  Note that this function does NOT return to the caller, but
  2496.   *  exits the program.
  2497.   */
  2498.  
  2499.  VOID SayExitApp( nRet )
  2500.      INT         nRet;
  2501.  {
  2502.      if( hWndWhatFrame )
  2503.          WinDestroyWindow( hWndWhatFrame );
  2504.  
  2505.      if( hMsgQ )
  2506.          WinDestroyMsgQueue( hMsgQ );
  2507.  
  2508.      if( hAB )
  2509.  
  2510.          WinTerminate( hAB );
  2511.  
  2512.      exit( nRet );
  2513.  }
  2514.  
  2515.  /*  Fills a specified rectangle with the background color.
  2516.   *  Checks that the rectangle is non-empty first.
  2517.   */
  2518.  
  2519.  VOID SayFillRect( hPS, xLeft, xBottom, xRight, xTop )
  2520.      HPS         hPS;
  2521.      SHORT       xLeft;
  2522.      SHORT       xBottom;
  2523.      SHORT       xRight;
  2524.      SHORT       xTop;
  2525.  
  2526.  {
  2527.      RECT        rcFill;
  2528.  
  2529.      if( xLeft >= xRight  ||  xBottom >= xTop )
  2530.  
  2531.          return;
  2532.  
  2533.      WinSetRect( hAB, &rcFill, xLeft, xBottom, xRight, xTop );
  2534.  
  2535.      WinFillRect(
  2536.          hPS,
  2537.          &rcFill,
  2538.          WinQuerySysColor( hAB, SCLR_WINDOW )
  2539.      );
  2540.  }
  2541.  
  2542.  /*  Initializes the application.  */
  2543.  
  2544.  BOOL SayInitApp()
  2545.  {
  2546.      HPS         hPS;
  2547.      BOOL        bOK;
  2548.  
  2549.      hAB = WinInitialize();
  2550.      if( ! hAB )
  2551.  
  2552.          return FALSE;
  2553.  
  2554.      hMsgQ = WinCreateMsgQueue( hAB, 0 );
  2555.      if( ! hMsgQ )
  2556.          return FALSE;
  2557.  
  2558.      WinLoadString(
  2559.          hAB, NULL, STR_NAME,  szAppName, sizeof(szAppName)
  2560.      );
  2561.      WinLoadString(
  2562.          hAB, NULL, STR_TITLE, szTitle,   sizeof(szTitle)
  2563.      );
  2564.      WinLoadString(
  2565.          hAB, NULL, STR_WHAT,  szText,    sizeof(szText)
  2566.      );
  2567.  
  2568.      bOK = WinRegisterClass(
  2569.          hAB,
  2570.          szAppName,
  2571.          (LPFNWP)SayWhatWndProc,
  2572.          CS_SYNCPAINT,
  2573.          0,
  2574.  
  2575.          NULL
  2576.      );
  2577.      if( ! bOK )
  2578.          return FALSE;
  2579.  
  2580.      hWndWhatFrame = WinCreateStdWindow(
  2581.          (HWND)NULL,
  2582.          FS_TITLEBAR | FS_SYSMENU |
  2583.              FS_MENU | FS_MINMAX | FS_SIZEBORDER,
  2584.          szAppName,
  2585.          szTitle,
  2586.          0L,
  2587.          (HMODULE)NULL,
  2588.          MENU_WHAT,
  2589.          &hWndWhat
  2590.      );
  2591.  
  2592.      if( ! hWndWhatFrame )
  2593.          return FALSE;
  2594.  
  2595.      WinShowWindow( hWndWhat, TRUE );
  2596.  
  2597.      return TRUE;
  2598.  }
  2599.  
  2600.  /*  Initializes one scroll bar in the control dialog.  */
  2601.  
  2602.  VOID SayInitBar( hWndDlg, idBar, nValue, nMin, nMax )
  2603.      HWND        hWndDlg;
  2604.      SHORT       idBar;
  2605.      SHORT       nValue;
  2606.      SHORT       nMin;
  2607.  
  2608.      SHORT       nMax;
  2609.  {
  2610.      HWND        hWndBar;
  2611.  
  2612.      hWndBar = WinWindowFromID( hWndDlg, idBar );
  2613.  
  2614.      WinSendDlgItemMsg(
  2615.          hWndDlg,
  2616.          idBar,
  2617.          SBM_SETSCROLLBAR,
  2618.  
  2619.          (LONG)nValue,
  2620.          MAKELONG( nMin, nMax )
  2621.      );
  2622.  
  2623.      WinSetDlgItemShort( hWndDlg, idBar+1, nValue, FALSE );
  2624.  }
  2625.  
  2626.  /*  Invalidates the text within the main window, adjusting the
  2627.   *  text rectangle if it's gone out of bounds.
  2628.   */
  2629.  
  2630.  VOID SayInvalidateText( hWnd )
  2631.      HWND        hWnd;
  2632.  {
  2633.      SayLimitTextPos( hWnd );
  2634.      WinInvalidateRect( hWnd, &rcText );
  2635.  }
  2636.  
  2637.  /*  Checks the text position against the window client area
  2638.   *  rectangle. If it's moved off the window in any direction,
  2639.   *  forces it back inside, and also reverses the ptAdvance value
  2640.   *  for that direction so it will 'bounce' off the edge. Handles
  2641.   *  both the iconic and open window cases.
  2642.   */
  2643.  
  2644.  VOID SayLimitTextPos( hWnd )
  2645.      HWND        hWnd;
  2646.  {
  2647.      RECT        rcClient;
  2648.      POINT       ptTextSize;
  2649.  
  2650.      ptTextSize = ptCharSize;
  2651.  
  2652.      if( ! bIconic ) {
  2653.          npszText = szText;
  2654.          ptTextSize.x *= strlen(szText);
  2655.      }
  2656.  
  2657.      WinQueryWindowRect( hWndWhat, &rcClient );
  2658.  
  2659.      if( rcText.xLeft > rcClient.xRight - ptTextSize.x ) {
  2660.          rcText.xLeft = rcClient.xRight - ptTextSize.x;
  2661.          ptAdvance.x = -ptAdvance.x;
  2662.      }
  2663.  
  2664.      if( rcText.xLeft < rcClient.xLeft ) {
  2665.          rcText.xLeft = rcClient.xLeft;
  2666.          ptAdvance.x = -ptAdvance.x;
  2667.      }
  2668.  
  2669.      if( rcText.yBottom < rcClient.yBottom ) {
  2670.          rcText.yBottom = rcClient.yBottom;
  2671.          ptAdvance.y = -ptAdvance.y;
  2672.      }
  2673.  
  2674.      if( rcText.yBottom > rcClient.yTop - ptTextSize.y ) {
  2675.          rcText.yBottom = rcClient.yTop - ptTextSize.y;
  2676.          ptAdvance.y = -ptAdvance.y;
  2677.      }
  2678.  
  2679.      rcText.xRight = rcText.xLeft   + ptTextSize.x;
  2680.      rcText.yTop   = rcText.yBottom + ptTextSize.y;
  2681.  
  2682.  }
  2683.  
  2684.  /*  Moves the text within the window, by invalidating the old
  2685.   *  position, adjusting rcText according to ptMove, and then
  2686.   *  invalidating the new position.
  2687.   */
  2688.  
  2689.  VOID SayMoveText( hWnd, ptMove )
  2690.      HWND        hWnd;
  2691.      POINT       ptMove;
  2692.  {
  2693.      SayInvalidateText( hWnd );
  2694.      rcText.xLeft   =
  2695.          ptMove.x - (rcText.xRight - rcText.xLeft    >> 1);
  2696.      rcText.yBottom =
  2697.          ptMove.y - (rcText.yTop   - rcText.yBottom  >> 1);
  2698.      SayInvalidateText( hWnd );
  2699.  }
  2700.  
  2701.  /*  Sets one of the dialog scroll bars to *pnValue.  If that value
  2702.   *  is out of range, limits it to the proper range and forces
  2703.   *  *pnValue to be within the range as well.
  2704.   */
  2705.  
  2706.  VOID SaySetBar( hWndDlg, pnValue, idBar )
  2707.      HWND        hWndDlg;
  2708.      SHORT *     pnValue;
  2709.      SHORT       idBar;
  2710.  {
  2711.      ULONG       lRange;
  2712.      SHORT       nValue;
  2713.      BOOL        bOK;
  2714.  
  2715.      lRange =
  2716.          WinSendDlgItemMsg(
  2717.              hWndDlg, idBar, SBM_QUERYRANGE, 0L, 0L
  2718.          );
  2719.  
  2720.      nValue =
  2721.          WinQueryDlgItemShort( hWndDlg, idBar+1, &bOK, FALSE );
  2722.  
  2723.      if(
  2724.          bOK  &&
  2725.          nValue >= LOUSHORT(lRange)  &&
  2726.          nValue <= HIUSHORT(lRange)
  2727.      ) {
  2728.          *pnValue = nValue;
  2729.          WinSendDlgItemMsg(
  2730.              hWndDlg, idBar, SBM_SETPOS, (LONG)nValue, (LONG)TRUE
  2731.          );
  2732.      } else {
  2733.          WinSetDlgItemShort(
  2734.              hWndDlg,
  2735.              idBar + 1,
  2736.              (INT)WinSendDlgItemMsg(
  2737.                  hWndDlg, idBar, SBM_QUERYPOS, 0L, 0L
  2738.              ),
  2739.              FALSE
  2740.          );
  2741.      }
  2742.  }
  2743.  
  2744.  /*  Dialog function for the control panel dialog box.  */
  2745.  
  2746.  ULONG FAR PASCAL SayWhatDlgProc( hWndDlg, wMsg, lParam1, lParam2 )
  2747.      HWND        hWndDlg;
  2748.      USHORT      wMsg;
  2749.      ULONG       lParam1;
  2750.      ULONG       lParam2;
  2751.  {
  2752.      HWND        hWndBar;
  2753.      RECT        rcWin;
  2754.      SHORT       n;
  2755.  
  2756.      switch( wMsg )
  2757.      {
  2758.      case WM_COMMAND:
  2759.          switch( LOUSHORT(lParam1) )
  2760.          {
  2761.            case IDOK:
  2762.              WinStopTimer( hAB, hWndWhat, TIMER_MOVE );
  2763.              WinQueryWindowText(
  2764.  
  2765.                  WinWindowFromID( hWndDlg, ITEM_WHAT ),
  2766.                  sizeof(szText),
  2767.                  szText
  2768.              );
  2769.              if( strlen(szText) = = 0 )
  2770.                  WinLoadString(
  2771.                      hAB, NULL, STR_WHAT, szText, sizeof(szText)
  2772.                  );
  2773.              npszText = szText;
  2774.              SaySetBar( hWndDlg, &nInterval, ITEM_INTBAR );
  2775.              SaySetBar( hWndDlg, &nDistance, ITEM_DISTBAR );
  2776.              WinStartTimer( hAB, hWndWhat, TIMER_MOVE, nInterval );
  2777.              WinInvalidateRect( hWndWhat, NULL );
  2778.              break;
  2779.  
  2780.            case IDCANCEL:
  2781.              WinDestroyWindow( hWndDlg );
  2782.              break;
  2783.  
  2784.            case ITEM_CLEAN:
  2785.            case ITEM_FLICKER:
  2786.              bCleanPaint = (BOOL)WinSendDlgItemMsg(
  2787.  
  2788.                  hWndDlg, ITEM_CLEAN,
  2789.                  BM_QUERYCHECK, 0L, 0L
  2790.              );
  2791.              break;
  2792.          }
  2793.          break;
  2794.  
  2795.      case WM_DESTROY:
  2796.          hWndPanel = NULL;
  2797.          break;
  2798.  
  2799.      case WM_HSCROLL:
  2800.          SayDoBarMsg(
  2801.              hWndDlg, LOUSHORT(lParam1),
  2802.              HIUSHORT(lParam2), LOUSHORT(lParam2)
  2803.          );
  2804.          break;
  2805.  
  2806.      case WM_INITDLG:
  2807.          WinQueryWindowRect( hWndDlg, &rcWin );
  2808.          WinMapWindowPoints(
  2809.              hWndWhat, (HWND)NULL, (LPPOINT)&rcWin.xLeft, 2
  2810.  
  2811.          );
  2812.          n = rcWin.xRight - ptScreenSize.x + ptCharSize.x;
  2813.          if( n > 0 )
  2814.              rcWin.xLeft -= n;
  2815.          rcWin.xLeft &= ~7;  /* byte align */
  2816.          n = rcWin.yTop - ptScreenSize.y + ptCharSize.y;
  2817.          if( n > 0 )
  2818.              rcWin.yBottom -= n;
  2819.          WinSetWindowPos(
  2820.              hWndDlg,
  2821.              (HWND)NULL,
  2822.              rcWin.xLeft,
  2823.              rcWin.yBottom,
  2824.              0, 0,
  2825.              SWP_MOVE
  2826.          );
  2827.          WinSetWindowText(
  2828.              WinWindowFromID( hWndDlg, ITEM_WHAT ), szText
  2829.          );
  2830.          WinSendDlgItemMsg(
  2831.              hWndDlg, ITEM_WHAT, EM_SETTEXTLIMIT,
  2832.              (LONG)sizeof(szText)-1, 0L
  2833.          );
  2834.          SayInitBar(
  2835.              hWndDlg, ITEM_INTBAR,  nInterval,
  2836.              MIN_INTERVAL, MAX_INTERVAL
  2837.          );
  2838.          SayInitBar(
  2839.              hWndDlg, ITEM_DISTBAR, nDistance,
  2840.              MIN_DISTANCE, MAX_DISTANCE
  2841.          );
  2842.          WinSendDlgItemMsg(
  2843.              hWndDlg, ITEM_CLEAN, BM_SETCHECK, (LONG)TRUE, 0L
  2844.          );
  2845.          break;
  2846.      }
  2847.      return WinDefDlgProc( hWndDlg, wMsg, lParam1, lParam2 );
  2848.  }
  2849.  
  2850.  /*  Painting procedure for the main window. Handles both the
  2851.   *  clean and flickery painting methods for demonstration
  2852.   *  purposes.
  2853.   */
  2854.  
  2855.  VOID SayWhatPaint( hWnd )
  2856.      HWND        hWnd;
  2857.  {
  2858.      HPS         hPS;
  2859.      RECT        rcPaint;
  2860.      GPOINT      gptText;
  2861.  
  2862.      hPS = WinBeginPaint( hWnd, (HPS)NULL, &rcPaint );
  2863.  
  2864.      GpiSetColor( hPS, lColor );
  2865.  
  2866.      SayLimitTextPos( hWnd );
  2867.  
  2868.      gptText.x = (LONG)rcText.xLeft;
  2869.      gptText.y = (LONG)rcText.yBottom;
  2870.  
  2871.      if( bCleanPaint ) {
  2872.  
  2873.          /* Clean painting, avoid redundant erasing */
  2874.  
  2875.          GpiSetBackMix( hPS, MIX_OVERPAINT );
  2876.  
  2877.          GpiCharStringAt(
  2878.              hPS,
  2879.              &gptText,
  2880.              (LONG)( bIconic ? 1 : strlen(szText) ),
  2881.              npszText
  2882.          );
  2883.  
  2884.          SayFillRect(
  2885.              hPS,
  2886.              rcPaint.xLeft,
  2887.              rcPaint.yBottom,
  2888.              rcText.xLeft,
  2889.              rcPaint.yTop
  2890.          );
  2891.  
  2892.          SayFillRect(
  2893.              hPS,
  2894.              rcText.xLeft,
  2895.              rcText.yTop,
  2896.              rcText.xRight,
  2897.              rcPaint.yTop
  2898.          );
  2899.  
  2900.          SayFillRect(
  2901.              hPS,
  2902.              rcText.xLeft,
  2903.              rcPaint.yBottom,
  2904.              rcText.xRight,
  2905.              rcText.yBottom
  2906.          );
  2907.  
  2908.          SayFillRect(
  2909.              hPS,
  2910.              rcText.xRight,
  2911.              rcPaint.yBottom,
  2912.              rcPaint.yRight,
  2913.              rcPaint.yTop
  2914.          );
  2915.  
  2916.       } else {
  2917.  
  2918.          /* Flickery painting, erase background and paint traditionally */
  2919.  
  2920.          WinFillRect(
  2921.              hPS,
  2922.              &rcPaint,
  2923.              WinQuerySysColor( hAB, SCLR_WINDOW )
  2924.          );
  2925.  
  2926.          GpiCharStringAt(
  2927.              hPS,
  2928.              &gptText,
  2929.              (LONG)( bIconic ? 1 : strlen(szText) ),
  2930.              npszText
  2931.          );
  2932.       }
  2933.  
  2934.       WinEndPaint( hPS );
  2935.  
  2936.       if( ! nInterval )
  2937.           SayInvalidateText( hWnd );
  2938.  }
  2939.  
  2940.  /*  Window function for the main window.  */
  2941.  
  2942.  ULONG FAR PASCAL SayWhatWndProc( hWnd, wMsg, lParam1, lParam2 )
  2943.      HWND        hWnd;
  2944.      USHORT      wMsg;
  2945.      ULONG       lParam1;
  2946.      ULONG       lParam2;
  2947.  {
  2948.      POINT       ptMouse;
  2949.      GSIZE       gsChar;
  2950.      HPS         hPS;
  2951.      LONG        ColorData[COLORDATAMAX];
  2952.      BOOL        bNowIconic;
  2953.  
  2954.      switch( wMsg )
  2955.      {
  2956.        case WM_BUTTON1DOWN:
  2957.          if( bMouseDown )
  2958.              break;
  2959.          WinStopTimer( hAB, hWnd, TIMER_MOVE );
  2960.  
  2961.          bMouseDown = TRUE;
  2962.          WinSetCapture( hAB, hWnd );
  2963.          ptMouse.x = LOUSHORT(lParam1);
  2964.          ptMouse.y = HIUSHORT(lParam1);
  2965.          SayMoveText( hWnd, ptMouse );
  2966.          return 0L;
  2967.  
  2968.        case WM_BUTTON1UP:
  2969.          if( ! bMouseDown )
  2970.              break;
  2971.          bMouseDown = FALSE;
  2972.          WinSetCapture( hAB, (HWND)NULL );
  2973.          ptMouse.x = LOUSHORT(lParam1);
  2974.          ptMouse.y = HIUSHORT(lParam1);
  2975.          SayMoveText( hWnd, ptMouse );
  2976.          WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
  2977.          return 0L;
  2978.  
  2979.        case WM_CHAR:
  2980.          if(
  2981.              ( LOUSHORT(lParam1) & KC_KEYUP )  ||
  2982.              ! ( LOUSHORT(lParam1) & KC_VIRTUALKEY )
  2983.  
  2984.          ) {
  2985.              break;
  2986.          }
  2987.          SayInvalidateText( hWnd );
  2988.          switch( HIUSHORT(lParam2) )
  2989.          {
  2990.            case VK_LEFT:
  2991.              rcText.xLeft -= ptCharSize.x;
  2992.              ptAdvance.x   = -1;
  2993.              ptAdvance.y   = 0;
  2994.              break;
  2995.            case VK_RIGHT:
  2996.              rcText.xLeft += ptCharSize.x;
  2997.              ptAdvance.x   = 1;
  2998.              ptAdvance.y   = 0;
  2999.              break;
  3000.            case VK_UP:
  3001.              rcText.yBottom -= ptCharSize.y >> 1;
  3002.              ptAdvance.x     = 0;
  3003.              ptAdvance.y     = -1;
  3004.              break;
  3005.            case VK_DOWN:
  3006.  
  3007.              rcText.yBottom += ptCharSize.y >> 1;
  3008.              ptAdvance.x     = 0;
  3009.              ptAdvance.y     = 1;
  3010.              break;
  3011.            default:
  3012.              return 0L;
  3013.          }
  3014.          SayInvalidateText( hWnd );
  3015.          nDistLeft = nDistance;
  3016.          return 0L;
  3017.  
  3018.        case WM_COMMAND:
  3019.          switch( LOUSHORT(lParam1) )
  3020.          {
  3021.            case CMD_ABOUT:
  3022.              WinDlgBox(
  3023.                  (HWND)NULL, hWnd, (LPFNWP)SayAboutDlgProc,
  3024.                  NULL, DLG_ABOUT, NULL
  3025.              );
  3026.              return 0L;
  3027.  
  3028.            case CMD_EXIT:
  3029.  
  3030.              WinDestroyWindow( hWndWhatFrame );
  3031.              return 0L;
  3032.  
  3033.            case CMD_WHAT:
  3034.              if( hWndPanel ) {
  3035.                  WinSetWindowPos(
  3036.                      hWndPanel,
  3037.                      HWND_TOP,
  3038.                      0, 0, 0, 0,
  3039.                      SWP_ZORDER | SWP_ACTIVATE
  3040.                  );
  3041.              } else {
  3042.                  hWndPanel = WinLoadDlg(
  3043.                      (HWND)NULL,
  3044.                      (HWND)NULL,
  3045.                      (LPFNWP)SayWhatDlgProc,
  3046.                      NULL,
  3047.                      DLG_WHAT,
  3048.                      NULL
  3049.                  );
  3050.              }
  3051.          }
  3052.  
  3053.          return 0L;
  3054.  
  3055.        case WM_CREATE:
  3056.          /* find out character/screen sizes, number of colors */
  3057.          hPS = WinGetPS( hWnd );
  3058.          GpiQueryCharBox( hPS, &gsChar );
  3059.          GpiQueryColorData( hPS, (LONG)COLORDATAMAX, ColorData );
  3060.          WinReleasePS( hPS );
  3061.          lColorMax = ColorData[3];
  3062.          ptCharSize.x = gsChar.width;
  3063.          ptCharSize.y = gsChar.height;
  3064.          ptScreenSize.x =
  3065.            WinQuerySysValue( (HWND)NULL, SV_CXSCREEN );
  3066.          ptScreenSize.y =
  3067.            WinQuerySysValue( (HWND)NULL, SV_CYSCREEN );
  3068.          /* initialize timer */
  3069.          srand( (INT)time(NULL) );
  3070.          WinStartTimer( hAB, hWnd, TIMER_MOVE, nInterval );
  3071.          return 0L;
  3072.  
  3073.        case WM_DESTROY:
  3074.          if( hWndPanel )
  3075.  
  3076.              WinDestroyWindow( hWndPanel );
  3077.          WinPostQueueMsg( hMsgQ, WM_QUIT, 0L, 0L );
  3078.          return 0L;
  3079.  
  3080.        case WM_ERASEBACKGROUND:
  3081.          return 1L;  /* don't erase */
  3082.  
  3083.        case WM_MINMAX:
  3084.          bNowIconic = ( LOUSHORT(lParam1) = = SWP_MINIMIZE );
  3085.          if( bIconic != bNowIconic ) {
  3086.              bIconic = bNowIconic;
  3087.              if( bIconic )
  3088.                  WinStopTimer( hAB, hWnd, TIMER_CHAR );
  3089.              else
  3090.                  WinStartTimer( hAB, hWnd, TIMER_CHAR, 1000 );
  3091.          }
  3092.          return 1L;
  3093.  
  3094.        case WM_MOUSEMOVE:
  3095.          if( bMouseDown ) {
  3096.              ptMouse.x = LOUSHORT(lParam1);
  3097.              ptMouse.y = HIUSHORT(lParam1);
  3098.  
  3099.              SayMoveText( hWnd, ptMouse );
  3100.          }
  3101.          return 0L;
  3102.  
  3103.        case WM_PAINT:
  3104.          SayWhatPaint( hWnd );
  3105.          return 0L;
  3106.  
  3107.        case WM_SIZE:
  3108.          SayInvalidateText( hWnd );
  3109.          nDistLeft = 0;
  3110.          SayAdvanceTextPos( hWnd );
  3111.          return 0L;
  3112.  
  3113.        case WM_TIMER:
  3114.          switch( LOUSHORT(lParam1) ) {
  3115.              case TIMER_MOVE:
  3116.                  SayAdvanceTextPos( hWnd );
  3117.                  break;
  3118.              case TIMER_CHAR:
  3119.                  SayAdvanceTextChar( hWnd );
  3120.                  break;
  3121.  
  3122.          }
  3123.          return 0L;
  3124.      }
  3125.      return WinDefWindowProc( hWnd, wMsg, lParam1, lParam2 );
  3126.  }
  3127.  
  3128.  /*  Main function for the application.  */
  3129.  
  3130.  void cdecl main( nArgs, pArgs )
  3131.      INT         nArgs;
  3132.      PSZ         pArgs;
  3133.  {
  3134.      QMSG        qMsg;
  3135.  
  3136.      if( ! SayInitApp() )
  3137.          SayExitApp( 1 );
  3138.  
  3139.      while( WinGetMsg( hAB, &qMsg, (HWND)NULL, 0, 0 ) ) {
  3140.  
  3141.          if( hWndPanel  &&  WinProcessDlgMsg( hWndPanel, &qMsg ) )
  3142.  
  3143.              continue;
  3144.  
  3145.          WinDispatchMsg( hAB, &qMsg );
  3146.      }
  3147.  
  3148.      SayExitApp( 0 );
  3149.  }
  3150.  
  3151.  ████████████████████████████████████████████████████████████████████████████
  3152.  
  3153.  Programming Considerations in Porting to Microsoft XENIX System V/386
  3154.  
  3155.  ───────────────────────────────────────────────────────────────────────────
  3156.  Also see the related article:
  3157.    Demand Paging and Virtual Memory
  3158.  ───────────────────────────────────────────────────────────────────────────
  3159.  
  3160.  Martin Dunsmuir
  3161.  
  3162.  XENIX(R) System V/386 is the Microsoft version of the UNIX(R) Operating
  3163.  System ported to the Intel(R) 386 microprocessor. The product, which was
  3164.  released to OEMs in the summer of 1987, is the first from Microsoft to take
  3165.  full advantage of the features of the Intel 386 microprocessor. In
  3166.  particular, XENIX allows 32-bit applications to be written for the first
  3167.  time.
  3168.  
  3169.  Microsoft has been active in the UNIX business since its inception, and in
  3170.  fact, XENIX predates even MS-DOS(R) as a Microsoft product. Since 1983, the
  3171.  XENIX development effort has concentrated on the Intel microprocessors──the
  3172.  286 introduced by Intel in 1983 and more recently the 386. By concentrating
  3173.  on one instruction set and chip architecture, Microsoft has been able to
  3174.  develop the world's largest installed base of binary-compatible UNIX
  3175.  systems. Currently, XENIX accounts for about 70 percent of all UNIX
  3176.  licenses sold, or roughly 250,000 units, and more than 1000 different
  3177.  applications are available. Although these numbers may sound small when
  3178.  compared with the numbers quoted for MS-DOS, the majority of these systems
  3179.  are used in multiuser configurations supporting between 2 and 16 users.
  3180.  
  3181.  Most of the current XENIX installed base is on 286-based PCs such as the
  3182.  IBM(R) PC AT(R). XENIX is used primarily as a cost-effective solution for
  3183.  small businesses that require multiuser access; it has sold particularly
  3184.  well in vertical markets that lend themselves to a customized "systems
  3185.  solution." Two such markets are dentistry and accounting.
  3186.  
  3187.  There was great excitement when Intel informed Microsoft that it was
  3188.  planning the 386 chip-and Microsoft set out to find a way to take advantage
  3189.  of the chip's features within the XENIX environment. In particular,
  3190.  Microsoft wanted to give developers the ability to create 32-bit
  3191.  applications and provide support within the operating system for virtual
  3192.  memory and demand paging──features that can greatly increase the throughput
  3193.  of a computer system.
  3194.  
  3195.  Another major design goal was to be sure that existing XENIX 286 users were
  3196.  not prevented from moving up to the 386. Microsoft wanted its installed base
  3197.  to be able to take advantage of the increased performance without having to
  3198.  buy new versions of their applications. Since the 386 chip supports both 16-
  3199.  and 32-bit segments in its architecture, Microsoft has been able to create
  3200.  an environment in which both 16- and 32-bit programs can be executed
  3201.  simultaneously. The features of demand paging and virtual memory are
  3202.  available transparently to both 16- and 32-bit applications.
  3203.  
  3204.  Implementing support for segments independently of the paging subsystem
  3205.  provides full performance for old applications without slowing down the
  3206.  execution of the new 32-bit programs (see Figure 1).
  3207.  
  3208.  Support for 32-bit applications is the key to the continued success of
  3209.  XENIX. It opens the door to the creation of much more powerful programs, as
  3210.  well as making it easier for developers to move existing UNIX applications
  3211.  onto XENIX. UNIX 32-bit programs being ported to XENIX 286 often needed
  3212.  extensive rewriting to make them work well in a 16-bit environment. This
  3213.  article outlines the considerations that will help programmers in choosing
  3214.  between 16- and 32-bit program models when developing applications or
  3215.  migrating to XENIX System V/386.
  3216.  
  3217.  
  3218.  Small-Model Programs
  3219.  
  3220.  In 16-bit mode, as used on XENIX 286, a program is composed of two segments
  3221.  up to 64Kb in size. One segment contains program code; the other segment
  3222.  contains data (the stack is of fixed size and resides in the data segment).
  3223.  An example of a small-model program is shown in Figure 2.
  3224.  
  3225.  The program in Figure 2 has only one data and one code segment, so it is
  3226.  not necessary to change the contents of the segment registers while the
  3227.  program is running. At load time, the operating system initializes them
  3228.  to point to the memory in which the program executes (via the LDT). In
  3229.  particular, the compiler or assembler programmer does not have to take
  3230.  account of changing segment registers during the program execution.
  3231.  
  3232.  Also of note is the fact that in the small model, both integers and pointers
  3233.  to data objects are 16-bit quantities and therefore interchangeable. This is
  3234.  important because many programs implicitly make this assumption.
  3235.  Unfortunately, although many commands and utilities are less than 64Kb in
  3236.  size, most third-party applications are larger than this; they require
  3237.  multiple code and/or data segment support. Figure 3 shows a multisegment
  3238.  program.
  3239.  
  3240.  
  3241.  Large- and Middle-Model
  3242.  
  3243.  Programs can overflow the 64Kb limit with their code or their data, or both
  3244.  at the same time. Programs that exceed 64Kb of code but still have less than
  3245.  64Kb of data are called middle-model programs. Large-model programs exceed
  3246.  64Kb in both their code and data segments.
  3247.  
  3248.  
  3249.  Large Code
  3250.  
  3251.  The program is broken down by the linker into pieces that will fit into
  3252.  64Kb. If a program's code exceeds 64Kb, then it is necessary to place
  3253.  different parts of it in different segments. (Because the breaks occur on
  3254.  function boundaries, each piece can be less than 64Kb in size.) The compiler
  3255.  must also gener-ate code that automatically reloads the CS register when
  3256.  the thread of execution moves from one segment to another. This results in a
  3257.  program that is slightly slower than the equivalent small-model image. The
  3258.  effect is not too drastic because within each subroutine CS remains constant
  3259.  so the frequency of segment register reloads is relatively low in
  3260.  comparison with the number of instructions executed.
  3261.  
  3262.  
  3263.  Large Data
  3264.  
  3265.  Data structures are spread among a number of different segments when data
  3266.  exceeds 64Kb. Again, the linker fills each segment with data structures as
  3267.  fully as possible. The performance penalty to be paid for going to large
  3268.  data is much larger than in the case of code because the frequency of
  3269.  intersegment data accesses is generally much greater than that of
  3270.  intersegment branches. The C compiler does not know which segment a
  3271.  particular data structure resides in at compile time; this causes a large
  3272.  number of unnecessary intersegment calls to be generated. Another problem
  3273.  in moving to large model is the fact that the size of data pointers
  3274.  increases to 32 bits. This means that the size of an integer is no longer
  3275.  equal to the size of a pointer, and programs that rely on this equality,
  3276.  either implicitly or explicitly, break. This is one of the primary problems
  3277.  developers experience when porting existing 32-bit UNIX programs to XENIX
  3278.  286. A summary of the different models can be seen in Figure 4.
  3279.  
  3280.  
  3281.  Hybrid Model
  3282.  
  3283.  Using the 286, where 16-bit segments are the norm but most useful
  3284.  applications exceed 64Kb in size, it is important for programmers to
  3285.  understand how to design their programs to reduce the effect of the
  3286.  multiple segment accesses. One way of doing this is to select specific data
  3287.  structures to be placed in separate far segments, while keeping
  3288.  indiscriminately accessed data structures (and the stack if possible) in a
  3289.  single near segment. The code generated by the Microsoft(R) C compiler in
  3290.  this case is much more efficient because the far keyword, used to mark
  3291.  specific data structures, gives the compiler a hint as to when it should
  3292.  reload segment registers.
  3293.  
  3294.  Use of a hybrid model with carefully designed programs comes close to the
  3295.  performance of small-model programs, even though they exceed 64Kb in size.
  3296.  However, there is a down side to this approach──it makes programs inherently
  3297.  less portable between XENIX 286 and other UNIX environments. Also,
  3298.  converting an existing 32-bit UNIX application to a hybrid model is
  3299.  complicated by the differences in pointer and integer size that make large-
  3300.  model ports such a problem.
  3301.  
  3302.  
  3303.  32-Bit Programming
  3304.  
  3305.  In contrast to the complexity of a multisegment 286 program, the native 386
  3306.  program structure is very simple (see Figure 5). Each program consists of
  3307.  one code segment along with one data segment, and each segment can be very
  3308.  large in size. (The exact limit depends on the availability of real
  3309.  memory and swap space and is typically a few megabytes).
  3310.  
  3311.  Because the address space is large, it is not necessary to support multiple
  3312.  segments in 32-bit mode, either in the operating system or in the C
  3313.  compiler. When a program is loaded, all the segment registers are
  3314.  initialized to static values and remain unchanged while the program is
  3315.  executing. In 32-bit mode, the stack lives in the data segment and grows
  3316.  down to lower addresses, while the data segment extends upward to higher
  3317.  addresses.
  3318.  
  3319.  XENIX 386 programs are truly 32-bit; they support 32-bit integers and all
  3320.  pointers are 32 bits in length. This eases the problems of porting existing
  3321.  32-bit applications to XENIX 386 in 32-bit mode.
  3322.  
  3323.  Other advantages offered by the 32-bit mode of the 386 are more orthogonal
  3324.  registers and addressing modes, which allow better code generation and more
  3325.  register variables, plus extra instructions that improve in-line code
  3326.  generation. Thirty-two-bit programs generally exhibit a significant
  3327.  performance advantage over the 16-bit versions.
  3328.  
  3329.  
  3330.  New XENIX Applications
  3331.  
  3332.  The introduction of XENIX 386 no longer constrains the developer to the 16-
  3333.  bit architecture. If he chooses, he can develop his application in 32-bit
  3334.  mode. However, the choice between a 16- and a 32-bit architecture for a
  3335.  new application is not as simple as it appears at first glance. 32-bit
  3336.  programs will only execute on XENIX 386, whereas 16-bit applications will
  3337.  execute on both XENIX 286 and XENIX 386. The installed base of XENIX 386 is
  3338.  still small, but it is almost certain to exceed that of XENIX 286 in time. A
  3339.  16-bit application may be a better choice for developers who want to address
  3340.  the largest possible installed base. Let's look at the trade-offs that must
  3341.  be considered when making the choice between 16 and 32 bits.
  3342.  
  3343.  The developer should ask himself the following questions:
  3344.  
  3345.    ■  What is the size of the application, both code and data?
  3346.  
  3347.    ■  Is the application an existing UNIX program being ported to XENIX?
  3348.  
  3349.    ■  Is the application an existing MS-DOS program or aimed at the MS-DOS,
  3350.       OS/2, and UNIX markets?
  3351.  
  3352.    ■  For new applications, what is the target market for the application?
  3353.       Is it limited to XENIX or does it have wider appeal in other UNIX or
  3354.       DOS markets?
  3355.  
  3356.    ■  What are the application's performance requirements?
  3357.  
  3358.  
  3359.  Application Size
  3360.  
  3361.  In many ways size is the most important consideration; unfortunately for
  3362.  new applications it is most likely the hardest to answer. For a simple
  3363.  application it is probably wise to build the application first as a 32-
  3364.  bit program and then see if it will fit into 16 bits. At this point it
  3365.  should be remembered that large data is a much more serious performance
  3366.  limitation to 16-bit programs-programs with more than 16 bits of code but
  3367.  less than 16 bits of data can be built as 16-bit middle-model programs
  3368.  without serious performance degradation.
  3369.  
  3370.  Another approach that can be used to fit a more complex program into the
  3371.  64Kb address space is to break it down into a number of separate,
  3372.  communicating processes, each of which fits into the smaller address
  3373.  space. Not all programs are amenable to such an architecture. Breaking an
  3374.  application into pieces can also limit portability into the MS-DOS
  3375.  world.
  3376.  
  3377.  
  3378.  Portability
  3379.  
  3380.  Many developers of UNIX applications for UNIX systems other than XENIX 286
  3381.  have programs that are designed implicitly for the 32-bit world. This is
  3382.  because XENIX 286 is one of the few UNIX systems to run on a 16-bit
  3383.  processor. Even if size is not a consideration, the work required to port
  3384.  UNIX applications from 32 to 16 bits has often deterred developers from
  3385.  doing the port.
  3386.  
  3387.  Such developers are best advised to build their applications for XENIX
  3388.  386 only. Debugging those problems summarized earlier in this article is
  3389.  often too great an effort to warrant porting a program to XENIX 286. This
  3390.  extra development effort can be considered for a later release if market
  3391.  pressure is felt.
  3392.  
  3393.  When porting existing MS-DOS applications to UNIX, it is usually more
  3394.  feasible to build an application in 16 bits. This is certainly the best and
  3395.  easiest option if the application contains a significant amount of
  3396.  assembler code. Since the XENIX and MS-DOS macro assemblers accept the same
  3397.  source syntax in 16-bit mode, assembly code that is not environment-specific
  3398.  should port directly to XENIX.
  3399.  
  3400.  Traditionally, UNIX and MS-DOS applications markets have been separated by a
  3401.  wide gap in complexity. This is because the architecture of real-mode MS-
  3402.  DOS programs is very different from UNIX. With the advent of OS/2, the
  3403.  underlying support provided by the two operating systems is now comparable,
  3404.  so it may make sense for new applications to be developed that can easily
  3405.  be hosted in both XENIX and OS/2 environments. If this is the case, it makes
  3406.  more sense to build the application for the 16-bit environment common to
  3407.  both XENIX and OS/2 and to delay the development of a 32-bit application
  3408.  until a 32-bit version of OS/2 becomes available.
  3409.  
  3410.  Another consideration for simpler applications is the use of the C library
  3411.  calls supported by the Microsoft C Compiler under MS-DOS. These calls, which
  3412.  embody a subset of the UNIX C library calls, can make it relatively easy to
  3413.  build a program that can be simply rehosted in both XENIX and MS-DOS
  3414.  environments. A good example of this approach would be the Microsoft 286 C
  3415.  compiler itself, which is recompiled and linked with different run-time
  3416.  libraries for execution on MS-DOS or XENIX 286. The task of creating a
  3417.  common source code for both MS-DOS and XENIX versions of the compiler is
  3418.  greatly facilitated by the fact that the XENIX and MS-DOS linkers both
  3419.  accept the same relocatable format as input (although they generate a
  3420.  different executable file format).
  3421.  
  3422.  
  3423.  XENIX and UNIX Markets
  3424.  
  3425.  Building applications that port easily between XENIX 286 and other UNIX
  3426.  platforms has generally been difficult. It is prudent──if a source portable
  3427.  application is desired──to remain within the 32-bit world. The 32-bit XENIX
  3428.  386 environment is completely compatible with the System V Interface
  3429.  Definition (SVID), and thus there should be very little difficulty in moving
  3430.  a carefully designed program from XENIX 386 to other UNIX platforms.
  3431.  
  3432.  
  3433.  Performance
  3434.  
  3435.  Although performance is a combination of many factors, it is most strongly
  3436.  linked to the architecture of the program and to the inherent speed of the
  3437.  host computer. All architectural considerations being equal, a 32-bit
  3438.  program will execute faster than a 16-bit program on the same 386 CPU.
  3439.  Applications that are being ported from the earlier 286 or 8086 worlds onto
  3440.  the 386 will experience an increase in raw 16-bit performance, simply by
  3441.  running the code on a 386, that more than offsets the need to consider
  3442.  rehosting into 32-bit mode.
  3443.  
  3444.  For new XENIX applications, especially those being ported from other 32-bit
  3445.  processors, where a 16-bit port is a serious possibility, it is important to
  3446.  understand the performance degradation seen on the 386 between 16-bit and
  3447.  32-bit code. The operating system itself runs in 32-bit mode, and some part
  3448.  of a program's execution time is spent in this code. The decrease in speed
  3449.  when moving to 16 bits is not as great as a simple comparison of CPU-bound
  3450.  16 and 32 performance might indicate. Figure 8 shows the relative
  3451.  execution times of two small C programs, "Cpubound" and "IObound," built
  3452.  as small-16, middle-16, large-16, and small-32 programs on XENIX 386.
  3453.  Figures 6 and 7 show the source code of these programs .
  3454.  
  3455.  An analysis of Figure 8 shows that the 32-bit architecture offers a
  3456.  significant performance advantage for CPU-bound programs that do a mix of
  3457.  arithmetic, pointer processing, and function calls. There is no
  3458.  performance difference among the various 16- and 32-bit models chosen for
  3459.  I/O-bound activities where the processing is all within the kernel.
  3460.  Although the performance of a 16-bit application on XENIX 386 falls short
  3461.  of the 32-bit performance, it is still between two and three times greater
  3462.  than the performance when that program is run on an 8-Mhz 286. The
  3463.  difference in performance between the 386 host and the 286 target must be
  3464.  factored in when measuring 16-bit performance on XENIX 386.
  3465.  
  3466.  
  3467.  Conclusions
  3468.  
  3469.  When designing an application for the XENIX 386 environment, the developer
  3470.  must weigh a number of conflicting criteria. The foremost problem is
  3471.  whether to build the program in 16- or 32-bit mode. Further questions must
  3472.  address the intended market as well as the performance and portability
  3473.  required of the completed product. Lastly, it is important to consider
  3474.  future compatibility requirements.
  3475.  
  3476.  Microsoft and AT&T are currently working together to merge XENIX 386 and
  3477.  AT&T's UNIX System V/386 Release 3.2 into a single UNIX system that will be
  3478.  marketed jointly by the two companies. This Merged Product (MP) will
  3479.  support all the existing 286 and 386 executable formats common to UNIX and
  3480.  XENIX on the 386, thereby allowing all existing applications to run.
  3481.  
  3482.  The emphasis for developers using the Merged Product will be to establish
  3483.  the UNIX/386, 32-bit mode program interface as the standard for new
  3484.  applications. This standard will be a superset of the current XENIX System
  3485.  V/386 program interface, without the support for XENIX-specific system call
  3486.  extensions. This means that in the long run there will be one binary
  3487.  standard, developed and supported by Microsoft and AT&T, which will run on
  3488.  all 386 machines running UNIX, thereby stabilizing the market.
  3489.  
  3490.  Developers who would like their programs to be source compatible with the
  3491.  new binary standard may want to avoid the use of XENIX system call
  3492.  extensions before the Merged Product becomes available in mid-1988. This
  3493.  applies particularly to the use of 32-bit applications (see Figure 9).
  3494.  Although kernel support is provided for XENIX extensions in the MP, minimal
  3495.  development tools will be provided. Debugging support will be limited to
  3496.  the UNIX System V/386 Release 3.2 binary standard. Without exception, the
  3497.  functionality of the XENIX call extensions is supported within the
  3498.  framework of the UNIX program interface.
  3499.  
  3500.  
  3501.  Figure 1:  Page table entries are maintained in groups of 16. This allows a
  3502.             286 segment to expand to 64Kb without existing table entries.
  3503.  
  3504.      ░░░░░░░░░░░░░░░░Mapping 286 Programs under XENIX 386░░░░░░░░░░░░░░░░░░
  3505.  
  3506.                                            Data Page Table
  3507.                                      ╔══════════════════════════╗
  3508.            Selector                  ║  Available for Expansion ║
  3509.              LDT                     ╟──────────────────────────╢
  3510.       ╔════════════════╗             ╟──────────────────────────╢
  3511.   5FH ║     DS #2      ║             ║           32Kb           ║- 8 pages
  3512.       ╟────────────────╢             ╟──────────────────────────╢      mapped
  3513.   57H ║     DS #1      ║────────────║           64Kb           ║-16 pages
  3514.       ╟────────────────╢             ╚══════════════════════════╝      mapped
  3515.   4FH ║     TS #3      ║                   Text Page Table
  3516.       ╟────────────────╢             ╔══════════════════════════╗
  3517.   47H ║     TS #2      ║─────┐       ║          Unused          ║
  3518.       ╟────────────────╢     │       ╟──────────────────────────╢
  3519.   3FH ║     TS #1      ║──┐  │       ║          Unused          ║
  3520.       ╚════════════════╝  │  │       ╟──────────────────────────╢
  3521.                           │  │       ║            8Kb           ║- 2 pages
  3522.                           │  │       ╟──────────────────────────╢      mapped
  3523.                           │  └──────║            64Kb          ║-16 pages
  3524.                           │          ╟──────────────────────────╢      mapped
  3525.                           └─────────║            64Kb          ║-16 pages
  3526.                                      ╚══════════════════════════╝      mapped
  3527.  
  3528.  
  3529.  Figure 2:  A Small-Model 286 Program
  3530.  
  3531.        ┌────────────────────────────────────────────────────────┐
  3532.        │        Text Segment               Data Segment         │█
  3533.        │  ╔═════════════════════╗64Kb╔═════════════════════╗64Kb│█
  3534.        │  ║                     ║    ║                     ║    │█
  3535.        │  ║      Unused         ║    ║    Available heap   ║    │█
  3536.        │  ║                     ║    ║                     ║    │█
  3537.        │  ╟─────────────────────╢    ╟─────────────────────╢    │█
  3538.        │  ║                     ║    ║                     ║    │█
  3539.        │  ║                     ║    ║      heap (BSS)     ║    │█
  3540.        │  ║                     ║    ║                     ║    │█
  3541.        │  ║       Text          ║    ╟─────────────────────╢    │█
  3542.        │  ║    (fixed size)     ║    ║                     ║    │█
  3543.        │  ║                     ║    ║     Fixed stack     ║    │█
  3544.        │  ║                     ║    ║                     ║    │█
  3545.        │  ║                     ║    ╟─────────────────────╢    │█
  3546.        │  ║                     ║    ║                     ║    │█
  3547.        │  ║                     ║    ║  Initialized data   ║    │█
  3548.        │  ║                     ║    ║                     ║    │█
  3549.        │  ║                     ║    ║                     ║    │█
  3550.        │  ║                     ║    ║                     ║    │█
  3551.        │  ╚═════════════════════╝0   ╚═════════════════════╝0   │█
  3552.        │       Selector 3FH                Selector 47H         │█
  3553.        └────────────────────────────────────────────────────────┘█
  3554.          █████████████████████████████████████████████████████████
  3555.  
  3556.  
  3557.  Figure 3:  Large-Model 286 Program Layout
  3558.  
  3559.            ╔════════════════╗            ╔════════════════╗
  3560.            ║    Unused      ║            ║                ║
  3561.            ╟────────────────╢            ║   1st segment  ║
  3562.            ║     TS #3      ║            ║  available for ║
  3563.            ║      8Kb       ║            ║      heap      ║
  3564.         4FH╚════════════════╝         67H╚════════════════╝
  3565.  
  3566.            ╔════════════════╗            ╔════════════════╗
  3567.            ║                ║            ║                ║
  3568.            ║     TS #2      ║            ║     DS #2      ║
  3569.            ║      64Kb      ║            ║    far data    ║
  3570.            ║                ║            ║                ║
  3571.         47H╚════════════════╝         5FH╚════════════════╝
  3572.  
  3573.            ╔════════════════╗            ╔════════════════╗
  3574.            ║                ║            ║                ║
  3575.            ║     TS #1      ║            ║      DS #1     ║
  3576.            ║      64Kb      ║            ║  stack & data  ║
  3577.            ║                ║            ║                ║
  3578.         3FH╚════════════════╝         57H╚════════════════╝
  3579.  
  3580.  
  3581.  Figure 4:  Camparison of 286 Program Models
  3582.  
  3583.  Name of
  3584.  Model      Max. Text  Max. Data  Stack          Heap             Performance
  3585.  
  3586.  Small      <=64Kb     <=64Kb     Fixed (<64Kb)  In Data (<64Kb)  Best
  3587.  
  3588.  Middle     >64Kb      <=64Kb     Fixed          In Data (<64Kb)  Good
  3589.  
  3590.  Compact    <=64Kb     >64Kb      <=64Kb         >64Kb            Poor
  3591.  
  3592.  Large      >64Kb      >64Kb      <=64Kb         >64Kb            Poorest
  3593.  
  3594.  Hybrid
  3595.  Data       <=64Kb     >64Kb      Fixed (<64Kb)  <64Kb            Good
  3596.  
  3597.  
  3598.  Figure 5:  Program Layout in 386 Mode
  3599.  
  3600.                      ┌─────────────────────────────────────────────────────┐
  3601.                      │    Text Segment            Data Segment             │█
  3602.            The stack │ ╔═════════════════╗4Gb ╔═════════════════╗4Gb       │█
  3603.           grows down │ ║                 ║    ║     Video RAM   ║virtual   │█
  3604.        to 0 virtual, │ ║                 ║    ╟─────────────────╢          │█
  3605.       while the heap │ ║                 ║    ║      Unused     ║          │█
  3606.        grows up. The │ ║                 ║    ╟─────────────────╢          │█
  3607.           sum of the │ ║                 ║    ║   Shared Data   ║          │█
  3608.         mapped text, │ ║                 ║    ╟─────────────────║6400000H  │█
  3609.            data, and │ ║                 ║    ║    Available    ║          │█
  3610.         stack cannot │ ║     Unused      ║    ║    for heap     ║          │█
  3611.           exceed the │ ║                 ║    ║    expansion    ║          │█
  3612.         installation │ ║                 ║    ╟─────────────────║          │█
  3613.      dependent limit │ ║                 ║    ║      Heap       ║          │█
  3614.       (typically the │ ║                 ║    ╟─────────────────╢          │█
  3615.     sum of installed │ ║                 ║    ║ Initialized Data║          │█
  3616.         RAM plus the │ ║                 ║    ╟─────────────────╢          │█
  3617.              size of │ ╟─────────────────╢    ║     Stack       ║          │█
  3618.           the paging │ ║                 ║    ╟─────────────────╢1880000H  │█
  3619.              area on │ ║      Text       ║    ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║          │█
  3620.           the disk). │ ║                 ║    ║▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒║          │█
  3621.                      │ ╚═════════════════╝0   ╚═════════════════╝0         │█
  3622.                      └─────────────────────────────────────────────────────┘█
  3623.                        ██████████████████████████████████████████████████████
  3624.  
  3625.  
  3626.  Figure 7:  IObound.c
  3627.  
  3628.  /*
  3629.   * IObound.c
  3630.   */
  3631.  
  3632.  #define IMAX 100
  3633.  #define JMAX 100
  3634.  
  3635.  #define BFS 2
  3636.  char buffer[BFS];
  3637.  
  3638.  main()
  3639.  {
  3640.   int i, j;
  3641.   int fd;
  3642.  
  3643.   /* Create a Disk File */
  3644.   fd = creat("scratch", 0600);
  3645.  
  3646.   for(i=0; i<IMAX; i++){
  3647.      sync();
  3648.      for(j=0; j<JMAX; j++){
  3649.         write(fd, buffer, BFS);
  3650.      }
  3651.  }
  3652.  
  3653.  
  3654.  sync();
  3655.   /* Return to Beginning of the File */
  3656.   lseek(fd, 0, 0);
  3657.  
  3658.   for(i=0; i<IMAX; i++){
  3659.      sync();
  3660.      for(j=0; j<JMAX; j++){
  3661.         read(fd, buffer, BFS);
  3662.      }
  3663.   }
  3664.  
  3665.   /* Remove the File */
  3666.   unlink('scratch');
  3667.   exit(0);
  3668.  }
  3669.  
  3670.  
  3671.  Figure 6:  Cpubound.c
  3672.  
  3673.  /*
  3674.   *      Cpubound.c
  3675.   */
  3676.  #define IMAX 100
  3677.  #define JMAX 1000
  3678.  
  3679.  int id[IMAX];
  3680.  int jd[JMAX];
  3681.  
  3682.  main()
  3683.  {
  3684.   int i, j;
  3685.  
  3686.   for(i=0; i<IMAX; i++){
  3687.      id[i] = i;
  3688.      for(j=0; j<JMAX; j++){
  3689.         jd[i] = j; calli(id, jd, j);
  3690.      }
  3691.   }
  3692.   exit(0);
  3693.  }
  3694.   calli(i, j, c)
  3695.   int *i, *j;
  3696.   int c;
  3697.   {
  3698.  
  3699.    int t;
  3700.    int ti = i[c];
  3701.    int tj = j[c];
  3702.  
  3703.    while(ti- -)
  3704.       t += (*(i++))+(*(j++))+(tj-);
  3705.  
  3706.    return(t);
  3707.   }
  3708.  
  3709.  
  3710.  Figure 8:  Performance Table
  3711.  
  3712.  CPU Bound Performance (normalized)
  3713.  
  3714.                      Real time        User time     System time
  3715.  
  3716.  Small-Model  286    32.4 (0.59)      32.2          0.0
  3717.  Middle-Model 286    40.6 (0.47)      40.5          0.0
  3718.  Large-Model  286    57.5 (0.33)      57.4          0.0
  3719.  Small-Model  386    19.0 (1.00)      18.9          0.0
  3720.  
  3721.  I/O Bound Performace (normalized)
  3722.  
  3723.                      Real time        User time     System time
  3724.  
  3725.  Small-Model  286    38.5 (1.00)      0.3           13.3
  3726.  Middle-Model 286    38.6 (1.00)      0.7           12.8
  3727.  Large-Model  286    42.4 (0.91)      0.4           14.8
  3728.  Small-Model  386    38.4 (1.00)      0.2           12.4
  3729.  
  3730.  
  3731.  Figure 9:  XENIX System Call Extensions to be avoided for portability.
  3732.  
  3733. ╓┌──────────────┌────────────────────────────────────────────────────────────╖
  3734.  Entry Point    Function
  3735.  
  3736.  chsize         adjust file size
  3737.  
  3738.  creatsem       semaphore operations
  3739.  nbwaitsem      semaphore operations
  3740.  Entry Point    Function
  3741. nbwaitsem      semaphore operations
  3742.  opensem        semaphore operations
  3743.  sigsem         semaphore operations
  3744.  waitsem        semaphore operations
  3745.  
  3746.  execseg        execute data
  3747.  unexecseg      execute data
  3748.  
  3749.  ftime          obsolete UNIX time function
  3750.  
  3751.  locking        XENIX file locking
  3752.  
  3753.  nap            sleep for a short time
  3754.  
  3755.  proctl         process specific control function
  3756.  
  3757.  rdchk          check for input without reading
  3758.  
  3759.  sdenter        XENIX shared data extension
  3760.  sdfree         XENIX shared data extension
  3761.  Entry Point    Function
  3762. sdfree         XENIX shared data extension
  3763.  sdget          XENIX shared data extension
  3764.  sdgetv         XENIX shared data extension
  3765.  sdleave        XENIX shared data extension
  3766.  sdwaitv        XENIX shared data extension
  3767.  
  3768.  shutdn         shutdown system
  3769.  
  3770.  swapon         control paging devices
  3771.  
  3772.  
  3773.  ───────────────────────────────────────────────────────────────────────────
  3774.  Demand Paging and Virtual Memory
  3775.  ───────────────────────────────────────────────────────────────────────────
  3776.  
  3777.  Demand paging is a feature of the XENIX 386 operating system, built on top
  3778.  of the 386 chip architecture that allows a program to run even though all
  3779.  of its pages have not been loaded into memory. Instead, only those pages
  3780.  from the executable image stored on disk that are actually referenced by the
  3781.  program as it runs are loaded into memory.
  3782.  
  3783.  Whenever the program references a page that is not in memory, it causes a
  3784.  "page fault." The XENIX kernel acts in response by loading the requested
  3785.  page from the disk and restarting the faulting program. The effect of demand
  3786.  paging is to reduce the memory usage of a given program to those pages that
  3787.  it actually references during a particular invocation. This set of pages is
  3788.  called the "working set" and is usually smaller than the full size of the
  3789.  program, especially if that program is large.
  3790.  
  3791.  Demand paging occurs without any knowledge on the part of the application.
  3792.  For example, the second pass of the Microsoft 386 C compiler is
  3793.  approximately 300Kb; however its working set on a typical program is
  3794.  closer to 80Kb, depending on which Microsoft C language features are
  3795.  used.
  3796.  
  3797.  The effect of demand paging is to improve the throughput of the system on
  3798.  smaller memory configurations, and because it is not necessary to load
  3799.  programs into memory before they start execution, the latency of command
  3800.  execution can be greatly reduced.
  3801.  
  3802.  Virtual memory allows the real memory associated with a program's heap
  3803.  (allocated via malloc or sbrk calls) to be allocated on demand rather than
  3804.  at the time of the malloc or sbrk function call. A program can be assigned a
  3805.  large address space without incurring additional overhead for pages that
  3806.  would remain unused.
  3807.  
  3808.  When a program makes a memory allocation call, the kernel recognizes the
  3809.  change in the end of the virtual heap. However, it allocates no memory
  3810.  between the end of the old heap and the new location. It is only when a
  3811.  program accesses pages in the new heap region and causes a page fault that
  3812.  the kernel allocates empty pages of real memory. This feature is also
  3813.  called "zero-fill on demand."
  3814.  
  3815.  ████████████████████████████████████████████████████████████████████████████
  3816.  
  3817.  HEXCALC: An Instructive Pop-Up Calculator for Microsoft Windows
  3818.  
  3819.  Charles Petzold
  3820.  
  3821.  When you design a dialog box for either a Windows program or an OS/2
  3822.  Presentation Manager program, you have available a whole array of "control
  3823.  windows" that take the form of push buttons, check boxes, scroll bars, list
  3824.  boxes, edit fields, and text items. Simply list these controls in a dialog
  3825.  box template and they magically appear in the dialog box window when the
  3826.  dialog box is invoked.
  3827.  
  3828.  A little further into Windows programming, you realize that these control
  3829.  windows can also be plastered right onto the surface of your window's client
  3830.  area. All it takes is a call to CreateWindow specifying the window class and
  3831.  the style of the control. A demonstration of this technique was shown in my
  3832.  COLORSCR program. (See "A Simple Windows Application for Custom Color
  3833.  Mixing," MSJ, Vol. 2, No. 2.)
  3834.  
  3835.  Then you reach a frustrating impasse. What you really want to do is create a
  3836.  simple dialog box template that describes the size and position of the
  3837.  various controls, and use that template to put the controls on your window's
  3838.  client area. At first there doesn't appear to be any way to do this. The
  3839.  only Windows functions that use the dialog template are CreateDialog and
  3840.  DialogBox. But you want to have these controls on your main window, not on a
  3841.  dialog box.
  3842.  
  3843.  HEXCALC (see Figure 1) shows how this is done. HEXCALC is a pop-up window
  3844.  with 29 child window push-button controls. It does not contain even one
  3845.  CreateWindow call. Instead, it uses CreateDialog to create the program's
  3846.  main window. This window and all the child window push-button controls are
  3847.  defined in a dialog box template in HEXCALC's resource script. This article
  3848.  will take a close look at HEXCALC, then compares HEXCALCW, a version for
  3849.  Windows 2.0, with HEXCALCP, which runs under Presentation Manager in the
  3850.  OS/2 systems.
  3851.  
  3852.  
  3853.  Using HEXCALC
  3854.  
  3855.  HEXCALC is a ten-function infix-notation hexadecimal calculator with a
  3856.  full keyboard and mouse interface. It works with 32-bit unsigned long
  3857.  integers and does addition, subtraction, multiplication, division,
  3858.  remainders, bitwise AND, OR, and Exclusive-OR, as well as left and right
  3859.  bit shifts. The buttons for the operations use C notation, except for the
  3860.  bit shift buttons, which display one angle bracket rather than two.
  3861.  
  3862.  To do a calculation, type or click in the first number (up to eight
  3863.  hexadecimal digits) followed by the operation and then the second number.
  3864.  You can show the result by clicking the Equals button or by pressing either
  3865.  the keyboard Equals key or Enter key. To correct your entries, you can use
  3866.  either the BackSpace key, the left cursor movement key, or the Back button.
  3867.  Pressing Escape or clicking the result box clears the current entry.
  3868.  
  3869.  Results are always truncated to 32 bits, just as if you were performing the
  3870.  operation on two unsigned long integers in a C program. The only special
  3871.  handling is a check for division by zero before doing division or a
  3872.  remainder operation. In this case, HEXCALC sets the result to FFFFFFFF.
  3873.  
  3874.  
  3875.  Inspired by PIFEDIT
  3876.  
  3877.  I don't know how many Windows programmers have taken a close look at
  3878.  PIFEDIT, but it does some very interesting things. PIFEDIT's main window
  3879.  looks a lot like a full-screen dialog box, and in fact it is.
  3880.  
  3881.  All those static text fields, edit fields, check boxes, and radio buttons on
  3882.  the surface of PIFEDIT's main window are defined in a dialog box template.
  3883.  Like HEXCALC, PIFEDIT doesn't make any CreateWindow calls. PIFEDIT's main
  3884.  window is created instead with a call to CreateDialog. The messages to this
  3885.  window are processed by a window function in the PIFEDIT program.
  3886.  
  3887.  The technique demonstrated in PIFEDIT and HEXCALC can be used for any
  3888.  program that would otherwise call CreateWindow to create child window
  3889.  controls on the surface of its main window. These controls can include
  3890.  buttons, scroll bars, static text fields, edit fields, list boxes, and even
  3891.  controls that you design yourself. The technique works best when these
  3892.  child window controls are of a fixed size and position relative to the
  3893.  size of a system font character, because that's how they are defined in the
  3894.  dialog box template.
  3895.  
  3896.  This is not the technique that the CALC.EXE program uses, however. CALC
  3897.  draws its own buttons rather than using child window button controls. This
  3898.  results in a faster display, but CALC is then forced to do hit testing on
  3899.  the mouse-click messages. Because push-button controls do their own hit
  3900.  testing, HEXCALC can ignore all mouse messages.
  3901.  
  3902.  
  3903.  The HEXCALC Program
  3904.  
  3905.  The various parts of the Windows 2.0 version of HEXCALC are shown in
  3906.  Figures 2W-6W.
  3907.  
  3908.  With an installed version of the Microsoft C Compiler Version 4.0 or 5.0
  3909.  and the Windows Software Development Kit Version 2.0, you can create
  3910.  HEXCALCW.EXE by running
  3911.  
  3912.    MAKE HEXCALCW
  3913.  
  3914.  Let's begin by taking a look at the HEXCALCW.RC resource script. This
  3915.  contains the dialog box template that defines HEXCALC's main window and all
  3916.  the push-button controls.
  3917.  
  3918.  
  3919.  Resource Script
  3920.  
  3921.  The dialog box template shown in the HEXCALCW.RC resource script describes
  3922.  the size and appearance of HEXCALC's window. The style bits are
  3923.  WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, and WS_MINIMIZEBOX. Except for
  3924.  WS_MINIMIZEBOX, these are normal for a modeless dialog box. The text
  3925.  appearing in the caption bar is "Hex Calculator".
  3926.  
  3927.  As is the case with all dialog box templates, the values in the DIALOG
  3928.  statement that specify the window's initial position and size are in units
  3929.  of 1/4 the width and 1/8 the height of a system font character. It is 102
  3930.  units (or 25 1/2 characters) wide and 122 units (or 15 1/4 characters)
  3931.  high.
  3932.  
  3933.  The 29 buttons displayed in the calculator are defined with PUSHBUTTON
  3934.  statements. The string in double quotes is the text that appears inside the
  3935.  button. The number that follows is the control ID. This is followed by the
  3936.  horizontal and vertical position of the control relative to the upper-left-
  3937.  hand corner of the dialog box's client area. The final two numbers indicate
  3938.  the horizontal and vertical size of the control. All the push buttons are
  3939.  a standard height: 14 units, or 1 3/4 characters.
  3940.  
  3941.  
  3942.  Control IDs
  3943.  
  3944.  The control ID is an integer that the child window uses to identify itself
  3945.  to its parent. The control ID also allows the parent window to send messages
  3946.  to the child window control (using SendDlgItemMessage) without knowing the
  3947.  window handle of the control. A parent can obtain the window handle of a
  3948.  control from its ID using GetDlgItem.
  3949.  
  3950.  Normally Windows programmers use identifiers defined in a header file for
  3951.  these control IDs. The header file is then included in both the program and
  3952.  the resource script using a #include statement.
  3953.  
  3954.  The control IDs I've chosen for these push buttons may appear to be random,
  3955.  but there really is a method to the madness. The control IDs have been set
  3956.  to the ASCII codes of the corresponding number, letter, or symbol that
  3957.  appears inside the push button.
  3958.  
  3959.  As will be seen in the HEXCALC.C listing, this is both a cheap and easy
  3960.  method to add a keyboard interface to the calculator. Clicking on a child
  3961.  window button with the mouse, the child window sends to its parent window
  3962.  (which is HEXCALC's main window) a WM_COMMAND message with wParam equal to
  3963.  the control ID. When the user presses a character key on the keyboard,
  3964.  HEXCALC's main window receives a WM_CHAR message with wParam equal to the
  3965.  ASCII code of the character. This means that the same logic can be used for
  3966.  both key presses and mouse clicks.
  3967.  
  3968.  
  3969.  The CLASS Statement
  3970.  
  3971.  The big difference between HEXCALC's dialog box template and most other
  3972.  dialog box templates is the presence of the CLASS statement. You can read in
  3973.  the Windows Programmer's Utility Guide, that (I quote it in its entirety)
  3974.  "This optional statement defines the class of the dialog box" and the
  3975.  parameter to CLASS "is an integer or a string (enclosed in double quotes)
  3976.  that identifies the dialog box class." At this point, perhaps some
  3977.  additional explanation is required.
  3978.  
  3979.  All windows created in Microsoft(R) Windows are based on a particular window
  3980.  class. Among other things, this class specifies a function (called the
  3981.  window function) that processes messages to the window. Normally in a
  3982.  Windows program you first register a window class and then create a window
  3983.  based on that class by calling CreateWindow.
  3984.  
  3985.  When you create a dialog box by calling CreateDialog or DialogBox, Windows
  3986.  normally uses a default window class that Windows itself defines. The
  3987.  function specified in this window class is internal to Windows. It is this
  3988.  window function that processes the messages to the dialog box. This
  3989.  function will pass many of the messages to a "dialog function" located in
  3990.  your program. The address of this dialog function is specified as a
  3991.  parameter to the CreateDialog or DialogBox function. That's the normal case.
  3992.  However, what we're doing in HEXCALC is somewhat different.
  3993.  
  3994.  By specifying a window class in the dialog box template, you're telling
  3995.  Windows not to use its own window class for dialog boxes created using this
  3996.  template. You're telling Windows to use a different window class,
  3997.  specifically the one indicated in the CLASS statement. This provides
  3998.  Windows with the address of a window function. It is this function that
  3999.  will receive messages for the dialog box. The window function, called
  4000.  WndProc, is part of HEXCALC.
  4001.  
  4002.  For an ordinary dialog box you wouldn't want to do this, because then you
  4003.  would be faced with the problem of duplicating all the logic required for
  4004.  changing the input focus among child window controls when the user presses
  4005.  the Tab key or cursor movement keys. But for a program like HEXCALC, this
  4006.  technique is ideal.
  4007.  
  4008.  
  4009.  Creating the Window
  4010.  
  4011.  Like most Windows programs, HEXCALC begins by registering a window class
  4012.  (HexCalcW) that among other things specifies the window function (WndProc)
  4013.  that will process messages to windows based on that class. But then, rather
  4014.  than call CreateWindow, HEXCALC calls CreateDialog, a function that usually
  4015.  creates a modeless dialog box.
  4016.  
  4017.  The second parameter to the CreateDialog function is the name of the dialog
  4018.  box template, which is also named HexCalcW. The third parameter to
  4019.  CreateDialog is normally the window handle of the dialog box's parent.
  4020.  Because you're using this dialog box as the main window in HEXCALC, this is
  4021.  set to 0.
  4022.  
  4023.  The fourth parameter to CreateDialog is normally a far pointer to the
  4024.  program's dialog function for the dialog box. This is the function to which
  4025.  some (but not all) messages processed by the actual dialog box window
  4026.  function are passed. Usually that window function is internal to Windows,
  4027.  but not when a class for the dialog box is specified in the template.
  4028.  Because the WndProc window function itself is part of HEXCALC, this dialog
  4029.  function is not needed. The fourth parameter is set to NULL.
  4030.  
  4031.  Now it's time to ask: What does Windows do during the CreateDialog call?
  4032.  Consider that Windows has access to the instance handle of the program and
  4033.  the name of the dialog box template. It gets this from the CreateDialog
  4034.  call:
  4035.  
  4036.    hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
  4037.  
  4038.  The szAppName variable is a character array that contains the name HexCalcW,
  4039.  the name of the dialog box template. This means that Windows can load the
  4040.  dialog box template into memory. The template (in its ASCII form) begins
  4041.  like this:
  4042.  
  4043.    HexCalcW DIALOG 32768, 0, 102, 122
  4044.      STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZE
  4045.      CLASS "HexCalcW"
  4046.      CAPTION "Hex Calculator"
  4047.  
  4048.  Using the parameters to the CreateDialog function along with the top of the
  4049.  dialog box template, Windows is able to call CreateWindow with the
  4050.  following parameters:
  4051.  
  4052.    hWnd = CreateWindow ("HexCalcW", "Hex Calculator",
  4053.      WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZE,
  4054.      32768,0, 102 * 4 / xChar, 122 * 8 / yChar, NULL, NULL, hInstance, NULL) ;
  4055.  
  4056.  This creates HEXCALC's top-level window. Assigning a value of 32768 to the
  4057.  initial x position specifies that Windows use the default position (when
  4058.  using a CreateWindow call in a program, CW_USEDEFAULT is the identifier).
  4059.  The xChar and yChar values shown here would be determined by Windows. They
  4060.  are the width and height of a system font character and are used to
  4061.  translate the dialog box coordinate and size units into pixels. The hWnd
  4062.  parameter returned from this call is eventually passed back to the program
  4063.  as the return value from CreateDialog.
  4064.  
  4065.  Still processing the CreateDialog call, Windows will then create child
  4066.  windows based on the PUSHBUTTON statements in the dialog box template. This
  4067.  is the first PUSHBUTTON statement:
  4068.  
  4069.    PUSHBUTTON "D", 68, 8, 24, 14, 14
  4070.  
  4071.  This becomes another CreateWindow call:
  4072.  
  4073.    hCtl = CreateWindow ("button", "D", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON |
  4074.      8 * 4 / xChar, 24 * 8 / yChar, 14 * 4 / xChar,
  4075.      14 * 8 / yChar, hWnd, 68, hInstance, NULL) ;
  4076.  
  4077.  Windows will call CreateWindow 29 times for each of the push buttons. You
  4078.  should note that the eighth parameter (the parent window) is set to hWnd.
  4079.  This will permit the child window controls to send WM_COMMAND messages to
  4080.  their parent window. The ninth parameter will be set to the control ID.
  4081.  
  4082.  Because the dialog box template specifies a window class, Windows does
  4083.  almost nothing else during the CreateDialog function except to call
  4084.  CreateWindow multiple times based on the template. When CreateDialog
  4085.  returns, all the windows have been created. The program proceeds normally
  4086.  just as if it had done all that work itself. HEXCALC has no other odd code.
  4087.  It has a normal message loop and a normal window function.
  4088.  
  4089.  So, in effect, HEXCALC is not very different from my earlier COLORSCR
  4090.  program. Both programs create a main window and several child window
  4091.  controls. The difference is that COLORSCR does it with explicit CreateWindow
  4092.  calls. HEXCALC simply gives Windows a dialog box template and says, "Here,
  4093.  you do this stuff. Tell me when you're finished."
  4094.  
  4095.  
  4096.  Window Function
  4097.  
  4098.  I'm not going to explain the actual calculator logic in HEXCALC because
  4099.  that's really not the point of this exercise. However, let's take a look at
  4100.  some of the code near the top of WndProc.
  4101.  
  4102.  Because the control IDs of the push buttons are set to the ASCII code of the
  4103.  push-button text, the keyboard interface is very simple. On a WM_CHAR
  4104.  message, the wParam parameter (the ASCII code of the key) is first converted
  4105.  to upper case and the Enter key is translated to an equals sign.
  4106.  
  4107.  HEXCALC determines if the key corresponds to a button by calling GetDlgItem.
  4108.  This function translates a control ID of a child window into the child
  4109.  window handle:
  4110.  
  4111.    hButton = GetDlgItem (hWnd, wParam)
  4112.  
  4113.  If hButton is 0, then the key does not correspond to a button, and HEXCALC
  4114.  beeps. Otherwise, HEXCALC flashes the button by sending it a couple of
  4115.  BM_SETSTATE messages:
  4116.  
  4117.    SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
  4118.    SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
  4119.  
  4120.  This adds a nice touch to the keyboard interface of HEXCALC and-as you can
  4121.  see-it's almost trivial.
  4122.  
  4123.  When WndProc processes WM_COMMAND messages, it always sets the input focus
  4124.  to the parent window:
  4125.  
  4126.    case WM_COMMAND: SetFocus (hWnd) ;
  4127.  
  4128.  Otherwise, the input focus would be shifted to one of the buttons whenever
  4129.  it was clicked with the mouse. This would alter the appearance of the button
  4130.  and also prevent WndProc from seeing subsequent WM_CHAR messages.
  4131.  
  4132.  
  4133.  The Presentation Manager Version
  4134.  
  4135.  The conversion of HEXCALC to the OS/2 Presentation Manager is relatively
  4136.  straightforward. The result──called HEXCALCP.EXE──is shown in Figures
  4137.  2PM-6PM.
  4138.  
  4139.  From a distance of about six feet, a Presentation Manager program looks the
  4140.  same as a Windows program. Structurally, the application program interfaces
  4141.  in the two environments are quite similar. However, on closer
  4142.  examination, lots of little changes are required. Some of these changes
  4143.  are described by Michael Geary in "Converting Windows Applications for
  4144.  Microsoft(R) OS/2 Presentation Manager," in this issue.
  4145.  
  4146.  The format of the dialog box template in HEXCALCP.RC is slightly different
  4147.  because it reflects the structure of "standard windows" in the
  4148.  Presentation Manager. The main window is a "frame" window, the client
  4149.  window is a child of the frame, and the push buttons are children of the
  4150.  client window. This parent-child relationship is described by the window
  4151.  definitions within nested brackets in the template. Messages to the client
  4152.  window in HEXCALCP are handled by the ClientWndProc window function.
  4153.  
  4154.  You will remember that HEXCALC must make a call to SetFocus whenever a
  4155.  button is clicked to prevent the button from keeping the keyboard input
  4156.  focus. Using the Presentation Manager, buttons can have a BS_NOMOUSEFOCUS
  4157.  style, which means that they will not receive the input focus when clicked
  4158.  with the mouse. The SetFocus call is made unnecessary by specifying the
  4159.  BS_NOMOUSEFOCUS style for the push buttons in the HEXCALCP.RC resource
  4160.  script.
  4161.  
  4162.  The keyboard interface in HEXCALCP is somewhat cleaner in the Presentation
  4163.  Manager version. First, all keyboard messages are WM_CHAR messages so
  4164.  HEXCALCP doesn't have to process WM_KEYDOWN messages. Secondly, if the key
  4165.  pressed corresponds to one of the buttons, then HEXCALCP sends the button a
  4166.  single BM_CLICK message:
  4167.  
  4168.    WinSendMsg (hWndButton, BM_CLICK, 0L, 0L) ;
  4169.  
  4170.  This message not only causes the button to flash, but also causes the button
  4171.  to send its owner a WM_COMMAND message. In the Windows version, the
  4172.  keyboard message falls through to be processed within the WM_COMMAND case.
  4173.  
  4174.  Termination works a little differently in the two programs. In Windows, the
  4175.  Close option on the system menu generates a WM_CLOSE message. If the message
  4176.  is passed on to DefWindowProc, then Windows destroys the window by calling
  4177.  DestroyWindow and the window function receives a WM_DESTROY message. The
  4178.  window function responds by posting a WM_QUIT message to itself with
  4179.  PostQuitMessage. This message causes the GetMessage function in WinMain to
  4180.  return 0, and the program terminates.
  4181.  
  4182.  In the Presentation Manager, the WM_CLOSE message is still generated by the
  4183.  system menu. If the program passes this message to WinDefWindowProc, then
  4184.  the Presentation Manager posts a WM_QUIT message to the message queue. As in
  4185.  Windows, this message causes the WinGetMsg call in main to return 0.
  4186.  However, the window still exists, so the program must explicitly destroy the
  4187.  window in main using a call to WinDestroyWindow. This function causes the
  4188.  Presentation Manager to destroy the frame window as well as all the
  4189.  children and other descendants of the frame.
  4190.  
  4191.  
  4192.  Figure 2W:  HEXCALCW Make File
  4193.  
  4194.  hexcalcw.obj: hexcalcw.c
  4195.       cl -c -D LINT_ARGS -Gsw -Os -W2 -Zp hexcalcw.c
  4196.  
  4197.  hexcalcw.res : hexcalcw.rc hexcalcw.ico
  4198.       rc -r hexcalcw.rc
  4199.  
  4200.  hexcalcw.exe: hexcalcw.obj hexcalcw.def hexcalcw.res
  4201.       link4 hexcalcw, /align:16, /map, slibw, hexcalcw
  4202.       rc hexcalcw.res
  4203.  
  4204.  
  4205.  Figure 2PM:  HEXCALCP Make File
  4206.  
  4207.  hexcalcp.obj: hexcalcp.c hexcalcp.h
  4208.       cl -c -D LINT_ARGS -G2sw -W2 -Zp hexcalcp.c
  4209.  
  4210.  hexcalcp.res : hexcalcp.rc hexcalcp.h
  4211.       rc -r hexcalcp.rc
  4212.  
  4213.  hexcalcp.exe: hexcalcp.obj hexcalcp.def hexcalcp.res
  4214.       link hexcalcp, /align:16, /map, slibc5 os2, hexcalcp
  4215.       rc hexcalcp.res
  4216.  
  4217.  
  4218.  Figure 3W:  HEXCALCW.RC Resource Script
  4219.  
  4220.  #include <windows.h>
  4221.  
  4222.  HexCalcW ICON hexcalcw.ico
  4223.  
  4224.  HexCalcW DIALOG 32768, 0, 102, 122
  4225.       STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
  4226.       CLASS "HexCalcW"
  4227.       CAPTION "Hex Calculator"
  4228.       BEGIN
  4229.            PUSHBUTTON "D",       68,  8,  24, 14, 14
  4230.            PUSHBUTTON "A",       65,  8,  40, 14, 14
  4231.            PUSHBUTTON "7",       55,  8,  56, 14, 14
  4232.            PUSHBUTTON "4",       52,  8,  72, 14, 14
  4233.            PUSHBUTTON "1",       49,  8,  88, 14, 14
  4234.            PUSHBUTTON "0",       48,  8, 104, 14, 14
  4235.            PUSHBUTTON "0",       27, 26,   4, 50, 14
  4236.            PUSHBUTTON "E",       69, 26,  24, 14, 14
  4237.            PUSHBUTTON "B",       66, 26,  40, 14, 14
  4238.            PUSHBUTTON "8",       56, 26,  56, 14, 14
  4239.            PUSHBUTTON "5",       53, 26,  72, 14, 14
  4240.            PUSHBUTTON "2",       50, 26,  88, 14, 14
  4241.            PUSHBUTTON "Back",     8, 26, 104, 32, 14
  4242.            PUSHBUTTON "C",       67, 44,  40, 14, 14
  4243.            PUSHBUTTON "F",       70, 44,  24, 14, 14
  4244.            PUSHBUTTON "9",       57, 44,  56, 14, 14
  4245.            PUSHBUTTON "6",       54, 44,  72, 14, 14
  4246.            PUSHBUTTON "3",       51, 44,  88, 14, 14
  4247.            PUSHBUTTON "+",       43, 62,  24, 14, 14
  4248.            PUSHBUTTON "-",       45, 62,  40, 14, 14
  4249.            PUSHBUTTON "*",       42, 62,  56, 14, 14
  4250.            PUSHBUTTON "/",       47, 62,  72, 14, 14
  4251.            PUSHBUTTON "%",       37, 62,  88, 14, 14
  4252.            PUSHBUTTON "Equals",  61, 62, 104, 32, 14
  4253.            PUSHBUTTON "&&",      38, 80,  24, 14, 14
  4254.            PUSHBUTTON "|",      124, 80,  40, 14, 14
  4255.            PUSHBUTTON "^",       94, 80,  56, 14, 14
  4256.            PUSHBUTTON "<",       60, 80,  72, 14, 14
  4257.            PUSHBUTTON ">",       62, 80,  88, 14, 14
  4258.       END
  4259.  
  4260.  
  4261.  Figure 3PM:  HEXCALCP.RC Resource Script
  4262.  
  4263.  #include <os2.h>
  4264.  #include "hexcalcp.h"
  4265.  
  4266.  WINDOWTEMPLATE ID_HEXCALC
  4267.      {
  4268.      FRAME "Hex Calculator", 1, 110, 40, 102, 122, WS_VISIBLE |
  4269.           FS_TITLEBAR | FS_SYSMENU | FS_MINBUTTON | FS_BORDER
  4270.          {
  4271.          WINDOW "", FID_CLIENT, 0, 0, 102, 122, "HexCalcP", WS_VISIBLE
  4272.              {
  4273.              PUSHBUTTON "D",       68,  8,  88, 14, 14, BS_NOMOUSEFOCUS
  4274.              PUSHBUTTON "A",       65,  8,  72, 14, 14, BS_NOMOUSEFOCUS
  4275.              PUSHBUTTON "7",       55,  8,  56, 14, 14, BS_NOMOUSEFOCUS
  4276.              PUSHBUTTON "4",       52,  8,  40, 14, 14, BS_NOMOUSEFOCUS
  4277.              PUSHBUTTON "1",       49,  8,  24, 14, 14, BS_NOMOUSEFOCUS
  4278.              PUSHBUTTON "0",       48,  8,   4, 14, 14, BS_NOMOUSEFOCUS
  4279.              PUSHBUTTON "0",       27, 26, 104, 50, 14, BS_NOMOUSEFOCUS
  4280.              PUSHBUTTON "E",       69, 26,  88, 14, 14, BS_NOMOUSEFOCUS
  4281.              PUSHBUTTON "B",       66, 26,  72, 14, 14, BS_NOMOUSEFOCUS
  4282.              PUSHBUTTON "8",       56, 26,  56, 14, 14, BS_NOMOUSEFOCUS
  4283.              PUSHBUTTON "5",       53, 26,  40, 14, 14, BS_NOMOUSEFOCUS
  4284.              PUSHBUTTON "2",       50, 26,  24, 14, 14, BS_NOMOUSEFOCUS
  4285.              PUSHBUTTON "Back",     8, 26,   4, 32, 14, BS_NOMOUSEFOCUS
  4286.              PUSHBUTTON "F",       70, 44,  88, 14, 14, BS_NOMOUSEFOCUS
  4287.              PUSHBUTTON "C",       67, 44,  72, 14, 14, BS_NOMOUSEFOCUS
  4288.              PUSHBUTTON "9",       57, 44,  56, 14, 14, BS_NOMOUSEFOCUS
  4289.              PUSHBUTTON "6",       54, 44,  40, 14, 14, BS_NOMOUSEFOCUS
  4290.              PUSHBUTTON "3",       51, 44,  24, 14, 14, BS_NOMOUSEFOCUS
  4291.              PUSHBUTTON "+",       43, 62,  88, 14, 14, BS_NOMOUSEFOCUS
  4292.              PUSHBUTTON "-",       45, 62,  72, 14, 14, BS_NOMOUSEFOCUS
  4293.              PUSHBUTTON "*",       42, 62,  56, 14, 14, BS_NOMOUSEFOCUS
  4294.              PUSHBUTTON "/",       47, 62,  40, 14, 14, BS_NOMOUSEFOCUS
  4295.              PUSHBUTTON "%",       37, 62,  24, 14, 14, BS_NOMOUSEFOCUS
  4296.              PUSHBUTTON "Equals",  61, 62,   4, 32, 14, BS_NOMOUSEFOCUS
  4297.              PUSHBUTTON "&",       38, 80,  88, 14, 14, BS_NOMOUSEFOCUS
  4298.              PUSHBUTTON "|",      124, 80,  72, 14, 14, BS_NOMOUSEFOCUS
  4299.              PUSHBUTTON "^",       94, 80,  56, 14, 14, BS_NOMOUSEFOCUS
  4300.              PUSHBUTTON "<",       60, 80,  40, 14, 14, BS_NOMOUSEFOCUS
  4301.              PUSHBUTTON ">",       62, 80,  24, 14, 14, BS_NOMOUSEFOCUS
  4302.              }
  4303.          }
  4304.      }
  4305.  
  4306.  
  4307.  Figure 4W:  HEXCALCW.DEF Module Definition File
  4308.  
  4309.  NAME           HexCalcW
  4310.  DESCRIPTION    'Hexadecimal Calculator(C) Charles Petzold 1987'
  4311.  STUB           'WINSTUB.EXE'
  4312.  CODE           MOVEABLE
  4313.  DATA           MOVEABLE MULTIPLE
  4314.  HEAPSIZE       1024
  4315.  STACKSIZE      4096
  4316.  EXPORTS        WndProc
  4317.  
  4318.  
  4319.  Figure 4PM:  HEXCALCP.DEF Module Definition File
  4320.  
  4321.  NAME           HEXCALCP
  4322.  DESCRIPTION    'Hexadecimal Calculator(C) Charles Petzold
  4323.  1987'
  4324.  STUB           'OS2STUB.EXE'
  4325.  HEAPSIZE       1024
  4326.  STACKSIZE      4096
  4327.  EXPORTS        ClientWndProc
  4328.  
  4329.  
  4330.  Figure 5W:
  4331.  
  4332.  There is no special header file equivalent
  4333.  for the Windows version.
  4334.  
  4335.  
  4336.  Figure 5PM:  HEXCALCP.H Header File
  4337.  
  4338.  #define ID_HEXCALC 1
  4339.  
  4340.  
  4341.  Figure 6W:  HEXCALCW.C is the Source Code Listing
  4342.  
  4343.  #include <windows.h>
  4344.  #include <limits.h>
  4345.  #include <stdlib.h>
  4346.  #include <string.h>
  4347.  #include <ctype.h>
  4348.  
  4349.  long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;
  4350.  
  4351.  int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
  4352.       HANDLE      hInstance, hPrevInstance;
  4353.       LPSTR       lpszCmdLine;
  4354.       int         nCmdShow;
  4355.       {
  4356.       static char szAppName [] = "HexCalcW" ;
  4357.       HWND        hWnd ;
  4358.       MSG         msg;
  4359.       WNDCLASS    wndclass ;
  4360.  
  4361.       if (!hPrevInstance)
  4362.            {
  4363.            wndclass.style          = CS_HREDRAW | CS_VREDRAW;
  4364.            wndclass.lpfnWndProc    = WndProc ;
  4365.            wndclass.cbClsExtra     = 0 ;
  4366.            wndclass.cbWndExtra     = 0 ;
  4367.            wndclass.hInstance      = hInstance ;
  4368.            wndclass.hIcon          = LoadIcon (hInstance, szAppName) ;
  4369.            wndclass.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
  4370.            wndclass.hbrBackground  = COLOR_WINDOW + 1 ;
  4371.            wndclass.lpszMenuName   = NULL ;
  4372.            wndclass.lpszClassName  = szAppName ;
  4373.  
  4374.            if (!RegisterClass (&wndclass))
  4375.                 return FALSE ;
  4376.            }
  4377.  
  4378.       hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
  4379.  
  4380.       ShowWindow (hWnd, nCmdShow) ;
  4381.  
  4382.       while (GetMessage (&msg, NULL, 0, 0))
  4383.            {
  4384.            TranslateMessage (&msg) ;
  4385.            DispatchMessage (&msg) ;
  4386.            }
  4387.       return msg.wParam ;
  4388.       }
  4389.  
  4390.  void ShowNumber (hWnd, dwNumber)
  4391.       HWND  hWnd ;
  4392.       DWORD dwNumber ;
  4393.       {
  4394.       char  szBuffer [20] ;
  4395.  
  4396.       SetDlgItemText (hWnd, VK_ESCAPE,
  4397.                           strupr (ltoa (dwNumber, szBuffer, 16))) ;
  4398.       }
  4399.  
  4400.  DWORD CalcIt (dwFirstNum, nOperation, dwNum)
  4401.       DWORD dwFirstNum, dwNum ;
  4402.       short nOperation ;
  4403.       {
  4404.       switch (nOperation)
  4405.            {
  4406.            case '=' : return dwNum ;
  4407.            case '+' : return dwFirstNum +  dwNum ;
  4408.            case '-' : return dwFirstNum -  dwNum ;
  4409.            case '*' : return dwFirstNum *  dwNum ;
  4410.            case '&' : return dwFirstNum &  dwNum ;
  4411.            case '|' : return dwFirstNum |  dwNum ;
  4412.            case '^' : return dwFirstNum ^  dwNum ;
  4413.            case '<' : return dwFirstNum << dwNum ;
  4414.            case '>' : return dwFirstNum >> dwNum ;
  4415.            case '/' : return dwNum ? dwFirstNum / dwNum : ULONG_MAX ;
  4416.            case '%' : return dwNum ? dwFirstNum % dwNum : ULONG_MAX ;
  4417.            default  : return 0L ;
  4418.            }
  4419.       }
  4420.  
  4421.  long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
  4422.       HWND         hWnd;
  4423.       unsigned     iMessage;
  4424.       WORD         wParam;
  4425.       LONG         lParam;
  4426.       {
  4427.       static BOOL  bNewNumber = TRUE ;
  4428.       static DWORD dwNumber, dwFirstNum ;
  4429.       static short nOperation = '=' ;
  4430.       HWND         hButton ;
  4431.  
  4432.       switch (iMessage)
  4433.            {
  4434.            case WM_KEYDOWN:             /* left arrow -> backspace */
  4435.                 if (wParam != VK_LEFT)
  4436.                      break ;
  4437.                 wParam = VK_BACK ;
  4438.                                               /* fall through */
  4439.            case WM_CHAR:
  4440.                 if ((wParam = toupper (wParam)) == VK_RETURN)
  4441.                      wParam = '=' ;
  4442.  
  4443.                 if (hButton = GetDlgItem (hWnd, wParam))
  4444.                      {
  4445.                      SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
  4446.                      SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
  4447.                      }
  4448.                 else
  4449.                      {
  4450.                      MessageBeep (0) ;
  4451.                      break ;
  4452.                      }
  4453.                                               /* fall through */
  4454.            case WM_COMMAND:
  4455.                 SetFocus (hWnd) ;
  4456.  
  4457.                 if (wParam == VK_BACK)                  /* backspace */
  4458.                      ShowNumber (hWnd, dwNumber /= 16) ;
  4459.  
  4460.                 else if (wParam == VK_ESCAPE)           /* escape */
  4461.                      ShowNumber (hWnd, dwNumber = 0L) ;
  4462.  
  4463.                 else if (isxdigit (wParam))             /* hex digit */
  4464.                      {
  4465.                      if (bNewNumber)
  4466.                           {
  4467.                           dwFirstNum = dwNumber ;
  4468.                           dwNumber = 0L ;
  4469.                           }
  4470.                      bNewNumber = FALSE ;
  4471.  
  4472.                      if (dwNumber <= ULONG_MAX >> 4)
  4473.                           ShowNumber (hWnd, dwNumber =
  4474.                                16 * dwNumber + wParam -
  4475.                                (isdigit (wParam) ? '0' : 'A' - 10)) ;
  4476.                      else
  4477.                           MessageBeep (0) ;
  4478.                      }
  4479.  
  4480.                 else                                    /* operation */
  4481.                      {
  4482.                      if (!bNewNumber)
  4483.                           ShowNumber (hWnd, dwNumber =
  4484.                                CalcIt (dwFirstNum, nOperation,
  4485.                                        dwNumber)) ;
  4486.                      bNewNumber = TRUE ;
  4487.                      nOperation = wParam ;
  4488.                      }
  4489.                 break ;
  4490.  
  4491.            case WM_DESTROY:
  4492.                 PostQuitMessage (0) ;
  4493.                 break ;
  4494.  
  4495.            default :
  4496.                 return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
  4497.            }
  4498.       return 0L ;
  4499.       }
  4500.  #include <windows.h>
  4501.  #include <limits.h>
  4502.  #include <stdlib.h>
  4503.  #include <string.h>
  4504.  #include <ctype.h>
  4505.  
  4506.  long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;
  4507.  
  4508.  int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
  4509.       HANDLE      hInstance, hPrevInstance;
  4510.       LPSTR       lpszCmdLine;
  4511.       int         nCmdShow;
  4512.       {
  4513.       static char szAppName [] = "HexCalcW" ;
  4514.       HWND        hWnd ;
  4515.       MSG         msg;
  4516.       WNDCLASS    wndclass ;
  4517.  
  4518.       if (!hPrevInstance)
  4519.            {
  4520.            wndclass.style          = CS_HREDRAW | CS_VREDRAW;
  4521.            wndclass.lpfnWndProc    = WndProc ;
  4522.            wndclass.cbClsExtra     = 0 ;
  4523.            wndclass.cbWndExtra     = 0 ;
  4524.            wndclass.hInstance      = hInstance ;
  4525.            wndclass.hIcon          = LoadIcon (hInstance, szAppName) ;
  4526.            wndclass.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
  4527.            wndclass.hbrBackground  = COLOR_WINDOW + 1 ;
  4528.            wndclass.lpszMenuName   = NULL ;
  4529.            wndclass.lpszClassName  = szAppName ;
  4530.  
  4531.            if (!RegisterClass (&wndclass))
  4532.                 return FALSE ;
  4533.            }
  4534.  
  4535.       hWnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
  4536.  
  4537.       ShowWindow (hWnd, nCmdShow) ;
  4538.  
  4539.       while (GetMessage (&msg, NULL, 0, 0))
  4540.            {
  4541.            TranslateMessage (&msg) ;
  4542.            DispatchMessage (&msg) ;
  4543.            }
  4544.       return msg.wParam ;
  4545.       }
  4546.  
  4547.  void ShowNumber (hWnd, dwNumber)
  4548.       HWND  hWnd ;
  4549.       DWORD dwNumber ;
  4550.       {
  4551.       char  szBuffer [20] ;
  4552.  
  4553.       SetDlgItemText (hWnd, VK_ESCAPE,
  4554.                           strupr (ltoa (dwNumber, szBuffer, 16))) ;
  4555.       }
  4556.  
  4557.  DWORD CalcIt (dwFirstNum, nOperation, dwNum)
  4558.       DWORD dwFirstNum, dwNum ;
  4559.       short nOperation ;
  4560.       {
  4561.       switch (nOperation)
  4562.            {
  4563.            case '=' : return dwNum ;
  4564.            case '+' : return dwFirstNum +  dwNum ;
  4565.            case '-' : return dwFirstNum -  dwNum ;
  4566.            case '*' : return dwFirstNum *  dwNum ;
  4567.            case '&' : return dwFirstNum &  dwNum ;
  4568.            case '|' : return dwFirstNum |  dwNum ;
  4569.            case '^' : return dwFirstNum ^  dwNum ;
  4570.            case '<' : return dwFirstNum << dwNum ;
  4571.            case '>' : return dwFirstNum >> dwNum ;
  4572.            case '/' : return dwNum ? dwFirstNum / dwNum : ULONG_MAX ;
  4573.            case '%' : return dwNum ? dwFirstNum % dwNum : ULONG_MAX ;
  4574.            default  : return 0L ;
  4575.            }
  4576.       }
  4577.  
  4578.  long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
  4579.       HWND         hWnd;
  4580.       unsigned     iMessage;
  4581.       WORD         wParam;
  4582.       LONG         lParam;
  4583.       {
  4584.       static BOOL  bNewNumber = TRUE ;
  4585.       static DWORD dwNumber, dwFirstNum ;
  4586.       static short nOperation = '=' ;
  4587.       HWND         hButton ;
  4588.  
  4589.       switch (iMessage)
  4590.            {
  4591.            case WM_KEYDOWN:             /* left arrow -> backspace */
  4592.                 if (wParam != VK_LEFT)
  4593.                      break ;
  4594.                 wParam = VK_BACK ;
  4595.                                               /* fall through */
  4596.            case WM_CHAR:
  4597.                 if ((wParam = toupper (wParam)) == VK_RETURN)
  4598.                      wParam = '=' ;
  4599.  
  4600.                 if (hButton = GetDlgItem (hWnd, wParam))
  4601.                      {
  4602.                      SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
  4603.                      SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
  4604.                      }
  4605.                 else
  4606.                      {
  4607.                      MessageBeep (0) ;
  4608.                      break ;
  4609.                      }
  4610.                                               /* fall through */
  4611.            case WM_COMMAND:
  4612.                 SetFocus (hWnd) ;
  4613.  
  4614.                 if (wParam == VK_BACK)                  /* backspace */
  4615.                      ShowNumber (hWnd, dwNumber /= 16) ;
  4616.  
  4617.                 else if (wParam == VK_ESCAPE)           /* escape */
  4618.                      ShowNumber (hWnd, dwNumber = 0L) ;
  4619.  
  4620.                 else if (isxdigit (wParam))             /* hex digit */
  4621.                      {
  4622.                      if (bNewNumber)
  4623.                           {
  4624.                           dwFirstNum = dwNumber ;
  4625.                           dwNumber = 0L ;
  4626.                           }
  4627.                      bNewNumber = FALSE ;
  4628.  
  4629.                      if (dwNumber <= ULONG_MAX >> 4)
  4630.                           ShowNumber (hWnd, dwNumber =
  4631.                                16 * dwNumber + wParam -
  4632.                                (isdigit (wParam) ? '0' : 'A' - 10)) ;
  4633.                      else
  4634.                           MessageBeep (0) ;
  4635.                      }
  4636.  
  4637.                 else                                    /* operation */
  4638.                      {
  4639.                      if (!bNewNumber)
  4640.                           ShowNumber (hWnd, dwNumber =
  4641.                                CalcIt (dwFirstNum, nOperation,
  4642.                                        dwNumber)) ;
  4643.                      bNewNumber = TRUE ;
  4644.                      nOperation = wParam ;
  4645.                      }
  4646.                 break ;
  4647.  
  4648.            case WM_DESTROY:
  4649.                 PostQuitMessage (0) ;
  4650.                 break ;
  4651.  
  4652.            default :
  4653.                 return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
  4654.            }
  4655.       return 0L ;
  4656.       }
  4657.  
  4658.  
  4659.  Figure 6PM:  HEXCALCP.C is the Source Code Listing
  4660.  
  4661.  #include <os2.h>
  4662.  #include <limits.h>
  4663.  #include <stdlib.h>
  4664.  #include <string.h>
  4665.  #include <ctype.h>
  4666.  #include "hexcalcp.h"
  4667.  
  4668.  ULONG EXPENTRY ClientWndProc (HWND, USHORT, ULONG, ULONG) ;
  4669.  
  4670.  main ()
  4671.  
  4672.        {
  4673.        static CHAR szClassName [] = "HexCalcP" ;
  4674.        HAB         hAB ;
  4675.        HMQ         hMQ ;
  4676.        HWND        hWnd ;
  4677.        QMSG        qmsg ;
  4678.  
  4679.        hAB = WinInitialize () ;
  4680.        hMQ = WinCreateMsgQueue (hAB, 0) ;
  4681.  
  4682.        WinRegisterClass (hAB, szClassName, ClientWndProc, 0L, 0, NULL) ;
  4683.  
  4684.        hWnd = WinLoadDlg (HWND_DESKTOP, HWND_DESKTOP, NULL, NULL,
  4685.               ID_HEXCALC, NULL) ;
  4686.  
  4687.        WinSetFocus (HWND_DESKTOP, WinWindowFromID (hWnd, FID_CLIENT)) ;
  4688.        while (WinGetMsg (hAB, &qmsg, NULL, 0, 0)) WinDispatchMsg (hAB,&qmsg);
  4689.  
  4690.        WinDestroyWindow (hWnd) ;
  4691.        WinDestroyMsgQueue (hMQ) ;
  4692.        WinTerminate (hAB) ;
  4693.  
  4694.        return 0 ;
  4695.        }
  4696.  
  4697.   void ShowNumber (hWnd, lNumber)
  4698.        HWND  hWnd ;
  4699.        ULONG lNumber ;
  4700.        {
  4701.        CHAR  szBuffer [20] ;
  4702.        HWND  hWndResult ;
  4703.  
  4704.        hWndResult = WinWindowFromID (hWnd, VK_ESCAPE) ;
  4705.  
  4706.        WinSetWindowText (hWndResult, strupr (ltoa (lNumber, szBuffer, 16))) ;
  4707.        }
  4708.  
  4709.   ULONG CalcIt (lFirstNum, iOperation, lNum)
  4710.        ULONG lFirstNum, lNum ;
  4711.        SHORT iOperation ;
  4712.        {
  4713.        switch (iOperation)
  4714.             {
  4715.             case '=' : return lNum ;
  4716.             case '+' : return lFirstNum +  lNum ;
  4717.             case '-' : return lFirstNum -  lNum ;
  4718.             case '*' : return lFirstNum *  lNum ;
  4719.             case '&' : return lFirstNum &  lNum ;
  4720.             case '|' : return lFirstNum |  lNum ;
  4721.             case '^' : return lFirstNum ^  lNum ;
  4722.             case '<' : return lFirstNum << lNum ;
  4723.             case '>' : return lFirstNum >> lNum ;
  4724.             case '/' : return lNum ? lFirstNum / lNum :
  4725.   ULONG_MAX ;
  4726.             case '%' : return lNum ? lFirstNum % lNum :
  4727.   ULONG_MAX ;
  4728.             default  : return 0L ;
  4729.             }
  4730.        }
  4731.  
  4732.   ULONG EXPENTRY ClientWndProc (hWnd, nMessage, lParam1, lParam2)
  4733.        HWND         hWnd ;
  4734.        USHORT       nMessage ;
  4735.        ULONG        lParam1 ;
  4736.        ULONG        lParam2 ;
  4737.        {
  4738.        static BOOL  bNewNumber = TRUE ;
  4739.        static ULONG lNumber, lFirstNum ;
  4740.        static SHORT iOperation = '=' ;
  4741.        HWND         hWndButton ;
  4742.        SHORT        idButton ;
  4743.  
  4744.        switch (nMessage)
  4745.             {
  4746.             case WM_CHAR:
  4747.                  if (lParam1 & KC_KEYUP)
  4748.                       break ;
  4749.  
  4750.                  if (HIUSHORT (lParam2) == VK_LEFT)   /* left arrow to */
  4751.                       LOUSHORT (lParam2) = VK_BACK ;  /* backspace   */
  4752.                  if (HIUSHORT (lParam2) == VK_RETURN) /*  return to     */
  4753.                       LOUSHORT (lParam2) = '=' ;      /* equals      */
  4754.                  if (LOUSHORT (lParam2) == 0)
  4755.                       break ;
  4756.  
  4757.                  LOUSHORT (lParam2) = toupper (LOUSHORT (lParam2)) ;
  4758.                  if (hWndButton = WinWindowFromID (hWnd, LOUSHORT (lParam2)))
  4759.                       WinSendMsg (hWndButton, BM_CLICK, 0L, 0L) ;
  4760.                  else
  4761.                       WinAlarm (HWND_DESKTOP, WA_ERROR) ;
  4762.                  return 1L ;
  4763.  
  4764.             case WM_COMMAND:
  4765.                  idButton = LOUSHORT (lParam1) ;
  4766.                  if (idButton == VK_BACK)                /* backspace */
  4767.                       ShowNumber (hWnd, lNumber /= 16) ;
  4768.                  else if (idButton == VK_ESCAPE)         /* escape    */
  4769.                       ShowNumber (hWnd, lNumber = 0L) ;
  4770.                  else if (isxdigit (idButton))           /* hex digit */
  4771.                       {
  4772.                       if (bNewNumber)
  4773.                            {
  4774.                            lFirstNum = lNumber ;
  4775.                            lNumber = 0L ;
  4776.                            }
  4777.                       bNewNumber = FALSE ;
  4778.                       if (lNumber <= ULONG_MAX >> 4)
  4779.                            ShowNumber (hWnd, lNumber =
  4780.                                 16 * lNumber + idButton -
  4781.                                 (isdigit (idButton) ? '0' : 'A' - 10)) ;
  4782.                       else
  4783.                            WinAlarm (HWND_DESKTOP, WA_ERROR) ;
  4784.                       }
  4785.  
  4786.                  else                                    /* operation */
  4787.                       {
  4788.                       if (!bNewNumber)
  4789.                            ShowNumber (hWnd, lNumber =
  4790.                                 CalcIt (lFirstNum, iOperation, lNumber)) ;
  4791.                       bNewNumber = TRUE ;
  4792.                       iOperation = idButton ;
  4793.                       }
  4794.                  break ;
  4795.             default :
  4796.                  return WinDefWindowProc (hWnd, nMessage, lParam1, lParam2) ;
  4797.             }
  4798.        return 0L ;
  4799.  
  4800.        }
  4801.  
  4802.  ████████████████████████████████████████████████████████████████████████████
  4803.  
  4804.  Effectively Using Far and Huge Data Pointers In Your Microsoft C Programs
  4805.  
  4806.  Kaare Christian
  4807.  
  4808.  Program pointers are one of the most important features of the C language,
  4809.  because they are often the only way to express your thoughts clearly and
  4810.  precisely. Addresses are critical in all programming languages, but most
  4811.  languages try to take care of addressing details for you, thereby hiding
  4812.  crucial information about what happens when your program executes. Only
  4813.  the C language brings these details to the fore, by allowing you to express
  4814.  pointer operations in the text of the program.
  4815.  
  4816.  In the early, heady days of C, pointers were 16-bit quantities. Newcomers to
  4817.  C had to learn pointer arithmetic as well as C's somewhat difficult notation
  4818.  for using pointers, but they did not have to worry much about the underlying
  4819.  machine, the PDP-11. Pointer operations are beautifully supported by the
  4820.  simple and consistent architecture of the PDP-11, but only within a 64Kb
  4821.  data space.
  4822.  
  4823.  The PC family of computers is based on Intel(R) processors, which use a
  4824.  segmented architecture. If your data fits within one 64Kb space
  4825.  (Microsoft(R) C small- or medium-model programs), then pointer operations
  4826.  on the PC will work efficiently and consistently, just as on the
  4827.  venerable PDP-11. (This discussion applies to the segmentation model of
  4828.  the 8086, 8088, 186, 188 and the real mode on the 286 and 386. Protected
  4829.  mode segmentation is different and is not discussed.) However, if you do
  4830.  need the extra memory that the PC architecture offers, then you must learn
  4831.  to use far and huge pointers.
  4832.  
  4833.  Microsoft C lets you mix data types within a single program. You can create
  4834.  a far pointer in a small-model program or a near pointer in a huge-model
  4835.  program. My experience has been with occasional use of pointers to far or
  4836.  huge data in small-model programs, because the software that I work on tends
  4837.  to be small except for a few large data structures. Many of my comments will
  4838.  apply to all pointers in a compact-, large-, or huge-model program, because
  4839.  in those models data pointers are, by default, far or huge. All of the
  4840.  sample programs in this article were compiled with Microsoft C Version 4.0,
  4841.  using the small model.
  4842.  
  4843.  Besides pointers to far and huge data, Microsoft C also has far and huge
  4844.  data types. In Microsoft C there are two methods for creating a very large
  4845.  array: you can declare it as a huge or far array of integers or you can
  4846.  declare a pointer to a huge or far integer and then use halloc, the huge
  4847.  memory allocation subroutine, to allocate the space. The second approach
  4848.  is preferable because it is more flexible. Declaring a huge array forces you
  4849.  to decide in advance the exact array size; using halloc to create the array
  4850.  allows your program to adjust to the actual amount of memory present on the
  4851.  computer when it executes. This article specifically examines pointers to
  4852.  huge and far data, although some of the caveats and much of the discussion
  4853.  on efficiency also applies to huge or far arrays.
  4854.  
  4855.  As a prerequisite for grasping the following material, you must first
  4856.  understand how pointers are used in C and C casts. If you really know the
  4857.  basics of these two topics, then the program in Figure 1 should be a snap.
  4858.  Before reading the next paragraph, look at the program and predict its
  4859.  output.
  4860.  
  4861.  The answer: 100 and 200. The first printf statement prints the number of
  4862.  integers in the interval between where ip points and where i is located.
  4863.  Since ip was assigned the location 100 integers past where i is stored,
  4864.  naturally the answer is 100. In the second printf statement, ip and the
  4865.  location of i are cast to unsigned and then subtracted. The cast doesn't
  4866.  change the bit patterns, but it does change the operation from a pointer
  4867.  subtraction to an ordinary unsigned subtraction. The second answer is 200
  4868.  because the two bit patterns differ by that amount. If you ask, why 200,
  4869.  it's because 100 integers at 2 bytes each makes 200. The statement ip += 100
  4870.  actually added 200 to ip's binary value. If you passed the quiz, then read
  4871.  on. If not, then you need to learn more about ordinary pointer operations
  4872.  before you can understand the extraordinary pointer operations of
  4873.  Microsoft C far and huge pointers.
  4874.  
  4875.  A pointer to far data, usually called a far pointer, is a 32-bit pointer
  4876.  whose arithmetic operations are performed by using just 16 bits. (See the
  4877.  summary in Figure 2.) A far pointer can point anywhere in the PC's address
  4878.  space, but the 16-bit arithmetic limit means that the memory region towards
  4879.  which it points must be less than 64Kb large. For instance, you could use a
  4880.  far pointer to access a dynamically allocated array of 25,000 integers, but
  4881.  you could not use that same far pointer to access an array of 25,000 floats
  4882.  or doubles. This is because the array of integers will take up less than
  4883.  64Kb, while the array of floats or doubles will take more.
  4884.  
  4885.  A pointer to huge data, called a huge pointer, is also a 32-bit pointer, but
  4886.  its arithmetic operations are performed by using all 32 bits. Thus a huge
  4887.  pointer can point anywhere, at anything. The trade-off, of course, is that
  4888.  huge pointers operate more slowly and use more code than far pointers.
  4889.  Adding an integer to a far pointer isn't much more work than adding two
  4890.  integers, but adding an integer to a huge pointer is roughly twice the work
  4891.  of adding two integers.
  4892.  
  4893.  Far and huge pointers are sometimes used in small- and medium-model programs
  4894.  to access addresses outside the program's 64Kb default data space. They
  4895.  might be used, for example, to access the video display buffer or to access
  4896.  dynamically allocated memory outside the 64Kb data space. In compact- or
  4897.  large-model programs, all data pointers are far pointers, unless you
  4898.  specifically ask for a huge or near pointer. In a huge-model program, all
  4899.  data pointers are huge unless you specifically request a near or far
  4900.  pointer.
  4901.  
  4902.  When you are declaring an ordinary pointer, such as the pointer ip in
  4903.  Figure 1, you place an asterisk in front of the name to signify that *ip is
  4904.  an integer; hence ip itself must be a pointer to integer. To declare a far
  4905.  or huge pointer, you must use the word far or huge with the asterisk. So
  4906.  you would say:
  4907.  
  4908.    int far *npfi;
  4909.    static int huge *hp1, huge *hp2;
  4910.  
  4911.  In a declaration or a cast, the word far or huge is conceptually glued to
  4912.  the asterisk; that's why the word huge appears twice in the second
  4913.  declaration, once for each asterisk. Note that the data type and storage
  4914.  class, static integer in this example, appear only once in the second
  4915.  declaration.
  4916.  
  4917.  The pointers created in the declaration above are designed to access data
  4918.  anywhere in memory, but the memory space for the pointers themselves (4
  4919.  bytes each) is allocated in the default data segment. The first pointer is
  4920.  named npfi because it is a near pointer to a far integer; the pointer itself
  4921.  is near data, but it points at far data. Nevertheless the vernacular term
  4922.  for npfi is far pointer, meaning a pointer to far data. You could create a
  4923.  near pointer (16-bit pointer) in the far data space, but not in the default
  4924.  data segment, by using the following declaration:
  4925.  
  4926.    int * far fpni;
  4927.  
  4928.  The pointer in this declaration is named fpni to remind you that it is a far
  4929.  pointer to a near integer; the pointer itself (2 bytes) is stored in a far
  4930.  data segment even though it can only point at integers inside the default
  4931.  data segment. This declaration cannot appear inside a procedure, not even
  4932.  inside main, because the declaration specifically tells the compiler to put
  4933.  the pointer in the far data segment, whereas automatic variables in a
  4934.  procedure are stored on the stack. However, adding the storage class static
  4935.  to the declaration would enable it to be placed inside a procedure.
  4936.  
  4937.  The words near, far, and huge are Microsoft extensions to the C language;
  4938.  they help Microsoft C wring the best performance out of the difficult Intel
  4939.  architecture. A declaration that looks like
  4940.  
  4941.    typename far * dataname
  4942.  
  4943.  creates a pointer in the near data segment to access something stored in a
  4944.  far data segment. Conversely, a declaration that looks like
  4945.  
  4946.    typename far dataname
  4947.  
  4948.  creates data in the far data segment. If the word far (or near or huge) is
  4949.  followed by an asterisk, the pointer is allocated locally, but is used to
  4950.  access distant data. If the word far (or near or huge) is followed by the
  4951.  name of a variable, then the variable itself is stored in the far (or near
  4952.  or huge) data segment. The Microsoft(R) C Compiler User's Guide has a chart
  4953.  with several examples of near/far/huge declarations.
  4954.  
  4955.  The first problem that you may encounter when using far and huge pointers in
  4956.  a small- or medium-model program is that the conventional pointer size for
  4957.  passing data pointers to procedures in these programs is 16 bits. Thus you
  4958.  can't pass a far or huge pointer to a procedure, such as strlen, that
  4959.  expects an ordinary near pointer. (If necessary, you can extract a library
  4960.  procedure for different models and link it in your program.) And if you
  4961.  write your own procedures, be careful to declare the parameter correctly
  4962.  as far or huge if you intend to pass it far or huge pointers. Far (or huge)
  4963.  and near pointers as procedure parameters simply can't be mixed.
  4964.  
  4965.  The second problem with far and huge pointers is that the C language
  4966.  standard (both the old Kernighan and Ritchie definition and the emerging
  4967.  ANSI standard) specifies that the difference between two pointers is an
  4968.  integer, and the result of the sizeof operator is also an integer. This can
  4969.  cause some problems, which the program in Figure 3 demonstrates.
  4970.  
  4971.  When the program in Figure 3 is executed with the value 10000 on the
  4972.  command line, the output is the following:
  4973.  
  4974.    Number of Elements: 10000
  4975.  
  4976.    1.10000 == (unsigned)(f2-f1)
  4977.    2.10000 == (long)(f2-f1)
  4978.    3.10000 == (unsigned long)(f2-f1)
  4979.    4.10000 == (long)(unsigned)(f2-f1)
  4980.  
  4981.  As you can see, everything looks OK. However when you increase the size of
  4982.  the allocation to 40000, which is a perfectly reasonable size for a
  4983.  character array accessed by a far pointer, the result is the following.
  4984.  
  4985.    Number of Elements: 40000
  4986.  
  4987.    1. 40000 == (unsigned)(f2-f1)
  4988.    2.-25536 == (long)(f2-f1)
  4989.    3.-25536 == (unsigned long)(f2-f1)
  4990.    4. 40000 == (long)(unsigned)(f2-f1)
  4991.  
  4992.  The problem in the second line of the output is that casting the result of
  4993.  the pointer subtraction (a signed integer) to a long is done with sign
  4994.  extension, thereby producing the wrong answer. The same problem could
  4995.  occur with a conventional array containing more than 32,767 elements,
  4996.  but conventional 32Kb element arrays are rare, whereas far and huge
  4997.  pointers exist specifically to manage large arrays.
  4998.  
  4999.  The problem in the third line is very subtle, and it took a trip or two to
  5000.  the Microsoft(R) C Compiler Language Reference Manual to understand the
  5001.  problem. A conversion from a signed integer (the result of subtracting f2
  5002.  from f1) to an unsigned long first does a sign extension to long, then
  5003.  converts that long to unsigned long. The solution is easy: simply cast the
  5004.  signed integer to unsigned, and then cast that to long (the fourth line).
  5005.  
  5006.  Figure 4 is a program for exploring the pointer arithmetic behavior of huge
  5007.  pointers. Since a huge pointer points at a large amount of storage, there
  5008.  are a few more cases to consider than with far pointers. When you run the
  5009.  program using the value of 10000 as the number of elements, the following
  5010.  is printed:
  5011.  
  5012.    Number of Long Elements: 10000
  5013.  
  5014.    1.      59152 == (unsigned)(h2-h1)
  5015.    2.      10000 == (unsigned)(long)(h2-h1)
  5016.    3.      -6384 == l1 = h2-h1
  5017.    4. 4294960912 == ul1 = h2-h1
  5018.    5.      10000 == (long)(h2-h1)
  5019.    6.      10000 == (unsigned long)(h2-h1)
  5020.    7.      59152 == (long)(unsigned)(h2-h1)
  5021.  
  5022.  The only cases that work correctly cast the result of h2-h1 immediately into
  5023.  a long or into unsigned long. The Microsoft manual warns you that without a
  5024.  cast to long, subtracting two huge pointers will result in an int, with the
  5025.  disastrous results shown above. In this particular case, the cast to long
  5026.  has an important semantic meaning that goes beyond its usual meaning of
  5027.  converting the item to the given type: the cast controls the precision of
  5028.  the result when subtracting huge pointers. The rub is that it requires
  5029.  Pointer differences to be an int, but an int (on a 16-bit PC) isn't large
  5030.  enough.
  5031.  
  5032.  The other distressing fact that you may have noticed is that the casts that
  5033.  allow you to subtract two far pointers are not the same as those that allow
  5034.  you to subtract two huge pointers. This means you must be doubly careful
  5035.  when you use these seemingly similar types; watch out for subtraction.
  5036.  
  5037.  Another difficulty with both far and huge pointers is the common assumption
  5038.  that pointers are well behaved. With the Intel segmented architecture,
  5039.  many different bit patterns allow a given far or huge pointer to access a
  5040.  given location. Thirty-two-bit pointers have two pieces, the high 16 bits,
  5041.  which are loaded into a segment register, and the low 16 bits, which are an
  5042.  offset. In the Intel CPU, the segment register value, shifted left four, is
  5043.  added to the ordinary register to form a 20-bit address. Thus the pointer
  5044.  0100:0000H (32-bit Intel pointers are often written with a colon in the
  5045.  middle for clarity) will access the location 1000H in memory. This is
  5046.  because the high 16 bits (0100H) are shifted left by four (01000H) and then
  5047.  added to the low 16 bits (0H) to produce the result 01000H. Figure 5 shows
  5048.  two other pointer values that will access location 1000H. The moral here is
  5049.  that comparing two pointers for equality may or may not work, depending on
  5050.  their bit patterns.
  5051.  
  5052.  The program in Figure 6 demonstrates the hazards of comparing far and huge
  5053.  pointers. The first printf statement will print yes or no, depending on
  5054.  whether the two pointers compared are equal. The second printf will print
  5055.  yes if the result of subtracting the two pointers is 0; otherwise it will
  5056.  print no. With ordinary pointers (PDP-11 pointers or 16-bit Intel pointers),
  5057.  the two results will always be the same. However, for two huge pointers
  5058.  that point to the same place, even though they have different bit patterns,
  5059.  the output of the program is the following:
  5060.  
  5061.    0100:0000 == 0080:0800 ? no
  5062.    (long)(0100:0000 -0080:0800) == 0L ? yes
  5063.  
  5064.  This strange result, two unequal pointers that have no difference,
  5065.  actually makes perfect sense. The C compiler compares pointers by
  5066.  comparing their bit patterns, but it must subtract them more laboriously.
  5067.  First the compiler forms two 20-bit linear addresses just as the CPU does
  5068.  in hardware, then subtracts the linear addresses and divides by the size of
  5069.  whatever the pointer accesses.
  5070.  
  5071.  If you do conventional things with your far and huge pointers, such as move
  5072.  them within an allocated chunk of memory, then comparisons will work fine.
  5073.  However, with anything unconventional such as hand-crafting pointers,
  5074.  picking them up from hardware devices, or acquiring them from foreign
  5075.  subroutine libraries, simple comparison is not wise. You can always compare
  5076.  two pointers by using the expression ((long)(p2 - p1) == 0), but it takes
  5077.  much longer than the typical expression p2==p1.
  5078.  
  5079.  You must keep in mind that the bit value in a far or huge pointer is not a
  5080.  byte offset from location 0. Instead it is in the Intel segment:offset
  5081.  format that must be added together (see Figure 3) to form a byte offset from
  5082.  location 0. Most reasons for converting a pointer to a byte offset from 0
  5083.  are very bad, but if you must, see Figure 7 to do it correctly. The output
  5084.  of the program in Figure 7 is shown in Figure 8.
  5085.  
  5086.  Conventional pointers are often cast to an unsigned, or to an unsigned long,
  5087.  when a byte offset from 0 is required. This will not work with a far or huge
  5088.  pointer, because of the segment:offset nature of the Intel architecture.
  5089.  The hptr2long macro is necessary to make sense of the address coded in the
  5090.  pointer. (The FP_SEG, FP_OFF macro calls in the hptr2long macro extract the
  5091.  segment and offset parts of a huge pointer. They are defined in the dos.h
  5092.  include file.)
  5093.  
  5094.  Now let's delve into the relative efficiency of far and huge pointers.
  5095.  Before looking at some assembly language code generated by the compiler for
  5096.  far and huge operations, let's think about what must be done in the two
  5097.  cases. For a far pointer, the C compiler can access something that it
  5098.  points towards by loading its high 16 bits into a segment register (usually
  5099.  ES), loading its low 16 bits into an ordinary register, and then accessing
  5100.  that location. For huge pointers the operation is the same.
  5101.  
  5102.  However, pointer arithmetic with far pointers is much simpler than that for
  5103.  huge pointers. For a far pointer, all arithmetic is done with just the low
  5104.  16 bits. All the compiler has to do is operate on the low 16 bits, just as
  5105.  if they were an ordinary near pointer. However, for a huge pointer the
  5106.  compiler must first do the operation on the low 16 bits, then it must shift
  5107.  the carry or borrow left 12 bits, and then it must perform the operation on
  5108.  the high 16 bits. That's a lot of work, which is sometimes assisted by
  5109.  subroutine calls to library routines.
  5110.  
  5111.  The program in Figure 9 invokes the four most common pointer operations:
  5112.  addition of an integer to a pointer, dereferencing a pointer, comparing
  5113.  two pointers, and subtracting two pointers. The code comes from one of my
  5114.  programs that manages a huge array using near and far pointers. The
  5115.  #defines at the top of the program allow it to be easily compiled using
  5116.  near, far, or huge pointers by defining either the word FAR or HUGE on the C
  5117.  compiler command line. The code generated for each pointer type is shown in
  5118.  Figure 10.
  5119.  
  5120.  The most striking feature of Figure 10 is that the overhead for using near
  5121.  and far pointers is similar; the heavy penalty does not occur until you
  5122.  start to use huge pointers. The most burdensome operation on huge pointers
  5123.  is subtraction; it takes nine in-line instructions, including one
  5124.  subroutine call to a support routine.
  5125.  
  5126.  In my work I needed huge pointers to manage an array of 50,000 long
  5127.  variables. Unfortunately the program calculated too slowly, so I was
  5128.  forced to speed things up. In my program most calculations only access a
  5129.  small region of the array, small enough to be accessed with a far pointer,
  5130.  although the array itself is so large that it has to be managed with huge
  5131.  pointers.
  5132.  
  5133.  My solution was to create a routine that would convert a huge pointer into a
  5134.  far pointer optimally. In my application, this meant that the resulting far
  5135.  pointer could be incremented and/or decremented several thousand times
  5136.  without having the low 16 bits overflow or underflow. Meeting that
  5137.  condition implies that the low 16 bits should be neither too small (danger
  5138.  of underflow) nor too large (danger of overflow).
  5139.  
  5140.  Suppose you have the huge integer pointer 2000:0000H. If you decrement it,
  5141.  the compiler will decrement the low 16 bits and then propagate the borrow
  5142.  into the high bits, yielding the correct answer: 1000:FFFEH. However if
  5143.  2000:000H is a far integer pointer and you decrement it, the compiler will
  5144.  simply decrement the low 16 bits, yielding a wrong answer: 2000:FFFEH. Far
  5145.  pointer arithmetic only breaks when there is an overflow or a borrow, so
  5146.  that far pointers whose low 16 bits are far from 0H and far from FFFFH (that
  5147.  is, in the neighborhood of 8000H) are quite safe.
  5148.  
  5149.  I wrote a routine called huge2far (see Figure 11) that rearranges a huge
  5150.  pointer to make its offset close to 8000H. This creates a far pointer that
  5151.  can access any element within a few thousand elements of where the pointer
  5152.  points initially. In other applications the optimal structuring of a near
  5153.  pointer might be different. For instance, in an application where the near
  5154.  pointer was only incremented (never decremented), an optional structure
  5155.  would make the low 16 bits as close to 0 as possible.
  5156.  
  5157.  Note that the huge2far routine doesn't work for all possible pointers. For
  5158.  example, there is only one pointer, 0000:0000H, that will access location 0.
  5159.  But in practice huge pointers always point beyond the program text and data,
  5160.  and for addresses in those regions the routine works as claimed.
  5161.  
  5162.  I use huge pointers in my program to manage the array, but whenever an
  5163.  intensive, local calculation is performed I convert a huge pointer into a
  5164.  far, do the calculation, and then, if the resulting pointer value must be
  5165.  saved, I convert the far pointer back into a huge by using a cast. It
  5166.  wouldn't be worthwhile to convert a huge to far to make a single array
  5167.  access, but if you want to sum the next thousand numbers in the array, you
  5168.  can convert huge to far and then use the far pointer a thousand times to
  5169.  access the next thousand array members. The time saved in my program is
  5170.  significant.
  5171.  
  5172.  The code in Figure 10 gives some additional clues about writing more
  5173.  efficient programs with huge pointers. Note that the most expensive
  5174.  operation, subtraction, is often used in loop bounds checks. Code such as
  5175.  the following is very common in C programs (p, q, head, and tail are
  5176.  pointers):
  5177.  
  5178.    while(p++<q)
  5179.       ∙
  5180.       ∙
  5181.       ∙
  5182.    for(p=head;p<tail;p++)
  5183.      ∙
  5184.      ∙
  5185.      ∙
  5186.  
  5187.  If these are huge pointers, each loop expends a lot of time comparing two
  5188.  pointers in each iteration. A more efficient solution would avoid the
  5189.  comparison on each pass through the loop, as in the following:
  5190.  
  5191.    cnt = q - p;
  5192.    while(p++, cnt--)
  5193.        ∙
  5194.        ∙
  5195.        ∙
  5196.    for(cnt=tail-p, p = head;
  5197.    cnt > 0; cnt--, p++)
  5198.        ∙
  5199.        ∙
  5200.        ∙
  5201.  
  5202.  Of course, you have to be careful that the value of p is not changed in the
  5203.  body of the loop.
  5204.  
  5205.  
  5206.  Figure 1:  Understanding Pointers
  5207.  
  5208.  main()
  5209.  {
  5210.      int *ip, i;
  5211.  
  5212.        ip = &i;
  5213.        ip += 100;
  5214.        printf("ip - &i: %d\n", ip - &i;
  5215.        printf("(unsigned)ip - (unsigned)&1: %u\n",
  5216.              (unsigned)ip - (unsigned)&i);
  5217.  }
  5218.  
  5219.  
  5220.  Figure 2:  Microsoft C Version 4. 0 Data Pointers
  5221.  
  5222.                         Near            Far              Huge
  5223.  
  5224.  
  5225.  Size (bits)            16              32               32
  5226.  
  5227.  Addressing             Anywhere in     Anywhere in      Anywhere
  5228.                         64Kb default    any 64Kb data
  5229.                         data segment    segment
  5230.  
  5231.  Pointer
  5232.  arithmetic             16 bits         16 bits          32 bits
  5233.  
  5234.  Microsoft C            Small           Compact          Huge
  5235.  defaults               Medium          Large
  5236.  
  5237.  
  5238.  Figure 3:  Pointer Subtraction
  5239.  
  5240.  /*
  5241.   * explore far pointers
  5242.   */
  5243.  #include <malloc.h>
  5244.  #include <stdlib.h>
  5245.  
  5246.  main(c,v)
  5247.  char *v[];
  5248.  {
  5249.       unsigned nelems;
  5250.       char far * f1, far * f2;
  5251.  
  5252.       if (c != 2) {
  5253.           printf("usage: %s elements_to_allocate\n", v[0]);
  5254.           exit();
  5255.       }
  5256.       nelems = (unsigned)atol(v[1]);
  5257.  
  5258.       f1 = (char far *)_fmalloc(nelems * sizeof(char));
  5259.       if (f1 == (char far *)0) {
  5260.           perror("_falloc failed:");
  5261.           exit();
  5262.       }
  5263.       f2 = f1 + nelems;
  5264.  
  5265.       printf("Number of Elements: %u\n", nelems);
  5266.       printf("1. %6u == (unsigned)(f2-f1)\n", (unsigned)(f2-f1));
  5267.       printf("2. %6ld == (long)(f2-f1)\n", (long)(f2-f1));
  5268.       printf("3. %6ld == (unsigned long)(f2-f1)\n",
  5269.              (unsigned long)(f2-f1));
  5270.       printf("4. %6ld == (long)(unsigned)(f2-f1)\n",
  5271.              (long)(unsigned)(f2-f1));
  5272.  }
  5273.  
  5274.  
  5275.  Figure 4:  Pointer Arithmetic Using Huge Pointers
  5276.  
  5277.  /*
  5278.   * explore huge pointers
  5279.   */
  5280.  #include <malloc.h>
  5281.  #include <stdlib.h>
  5282.  
  5283.  main(c,v)
  5284.  char *v[];
  5285.  {
  5286.       long nelems;
  5287.       long huge * h1, huge * h2;
  5288.       long l1;
  5289.       unsigned long ul1;
  5290.  
  5291.       if (c != 2) {
  5292.           printf("usage: %s elements_to_allocate\n", v[0]);
  5293.           exit();
  5294.       }
  5295.       nelems = atol(v[1]);
  5296.  
  5297.       h1 = (long huge *)halloc(nelems, sizeof(long));
  5298.       if (h1 == (long huge *)0) {
  5299.           perror("halloc failed:");
  5300.           exit();
  5301.       }
  5302.  
  5303.       h2 = h1 + nelems;
  5304.       printf("Number of Long Elements: %lu\n", nelems);
  5305.       printf("1. %12u == (unsigned)(h2-h1)\n", (unsigned)(h2-h1));
  5306.       printf("2. %12u == (unsigned)(long)(h2-h1)\n",
  5307.              (unsigned)(long)(h2-h1));
  5308.       printf("3. %12ld == l1 = h2-h1\n", l1=h2-h1);
  5309.       printf("4. %12lu == ul1 = h2-h1\n", ul1=h2-h1);
  5310.       printf("5. %12ld == (long)(h2-h1)\n", (long)(h2-h1));
  5311.       printf("6. %12lu == (unsigned long)(h2-h1)\n", (unsigned
  5312.              long)(h2-h1));
  5313.       printf("7. %12ld == (long)(unsigned)(h2-h1)\n",
  5314.              (long)(unsigned)(h2-h1));
  5315.  }
  5316.  
  5317.  
  5318.  Figure 5:  Three Distinct Pointer Values Point to Location 1000H
  5319.  
  5320.  Pointer                 0x01000000    0x00ff0010    0x00800800
  5321.  
  5322.  
  5323.  High 16 bits                0x0100        0x00ff          0x80
  5324.  
  5325.  
  5326.  Low 16 Bits                    0x0          0x10         0x800
  5327.  
  5328.  
  5329.  Intel segmentation         0x01000       0x00ff0         0x800
  5330.  
  5331.  Address computation           +0x0         +0x10        +0x800
  5332.                            ________      ________    __________
  5333.  Result                     0x01000       0x01000        0x1000
  5334.  
  5335.  
  5336.  Figure 6:  Comparing Far and Huge Pointers
  5337.  
  5338.  /*
  5339.   * equality of huge pointers
  5340.   */
  5341.  
  5342.  main()
  5343.  {
  5344.       int huge *h1, huge * h2;
  5345.  
  5346.       h1 = (int huge *)0x01000000;
  5347.       h2 = (int huge *)0x00800800;
  5348.  
  5349.       printf("%p == %p ? %s\n", h1, h2, h1 == h2 ? "yes" : "no");
  5350.       printf("(long)(%p - %p) == 0L ? %s\n", h1, h2,
  5351.           (long)(h1 - h2) == 0L ? "yes" : "no");
  5352.  }
  5353.  
  5354.  
  5355.  Figure 7:  Converting Pointers to a Byte Offset from 0
  5356.  
  5357.  /*
  5358.   * Byte offsets of huge pointers
  5359.   */
  5360.  
  5361.  #include <dos.h>
  5362.  #include <malloc.h>
  5363.  
  5364.  /* convert a MSC huge pointer into a byte offset from location 0 */
  5365.  #define hptr2long(p) (((unsigned long)FP_SEG(p))<<4 + (unsigned
  5366.                          long)FP_OFF(p))
  5367.  
  5368.  #define NELEM 5
  5369.  
  5370.  main()
  5371.  {
  5372.       int huge *hptr[NELEM];
  5373.       int i;
  5374.  
  5375.       printf("   Huge    Offset\n");
  5376.       printf("  Pointer  from 0\n");
  5377.       for(i=0;i<NELEM;i++) {
  5378.           hptr[i] = (int huge *)halloc(32768L, sizeof(int));
  5379.           printf("%p %7ld\n", hptr[i], hptr2long(hptr[i]));
  5380.       }
  5381.  }
  5382.  
  5383.  
  5384.  Figure 8:  The output of the program in Figure 7. The first pointer,
  5385.             209A:0000H, accesses byte 209A0H on the PC. Note that 209A0H is
  5386.             equal to 133536 decimal.
  5387.  
  5388.       Huge              Offset
  5389.       pointer           from 0
  5390.  
  5391.       209A:0000         133536
  5392.       309B:0000         199088
  5393.       409C:0000         264640
  5394.       509D:0000         330192
  5395.       609E:0000         395744
  5396.  
  5397.  
  5398.  Figure 9:  Invoking Common Pointer Operations
  5399.  
  5400.  #include <malloc.h>
  5401.  
  5402.  if defined (HUGE)
  5403.  #define PTYPE Huge
  5404.  #define MALLOC halloc(NELEM, sizeof(unsighned) )
  5405.  #define DTYPE long
  5406.  #elif defined (FAR)
  5407.  #define PTYPE far
  5408.  #define MALLOC _fmalloc(NELEM * sizeof(unsigned) )
  5409.  #define DTYPE unsigned
  5410.  #else
  5411.  #define PTYPE
  5412.  #define MALLOC malloc(NELEM * sizeof(unsighned) )
  5413.  #define DTYPE unsigned
  5414.  #endif
  5415.  
  5416.  main()
  5417.  {
  5418.      unsigned PTYPE *head, PTYPE *aspikes, PTYPE *end;
  5419.      unsigned time;
  5420.      DTYPE diff;
  5421.  
  5422.      head = (unsigned PTYPE *)MALLOC;
  5423.      aspikes = head;
  5424.      end = head + NELEM;
  5425.      while ((time = *aspikes) && aspikes++ < end)
  5426.          diff = (DTYPE) (end - aspikes);
  5427.  }
  5428.  
  5429.  
  5430.  Figure 10:  Compiler Code Generation for the Program in Figure 9 for Near,
  5431.              Far, and Huge Pointers
  5432.  
  5433. ╓┌────────────────────┌─────────────────────────┌────────────────────────┌───
  5434.  C                    NEAR                      FAR                      HUGE
  5435.  
  5436.  end = head + NELEM;  add ax,20000              add ax,20000             mov a
  5437.                       mov [bp-6],ax   ;end      mov [bp-8],ax  ;end      cwd
  5438.                                                 mov [bp-6],dx            add a
  5439.                                                                          adc d
  5440.                                                                          mov c
  5441.  C                    NEAR                      FAR                      HUGE
  5442.                                                                         mov c
  5443.                                                                          shl d
  5444.                                                                          add d
  5445.                                                                          mov [
  5446.                                                                          mov [
  5447.  
  5448.  time = *aspikes      mov bx,[bp-8]   ;aspikes  les bx,[bp-12] ;aspikes  les b
  5449.                       mov ax,[bx]               mov ax,es:[bx]           mov a
  5450.                       mov [bp-4],ax   ;time     mov [bp-4],ax  ;time     mov [
  5451.  
  5452.  aspikes++ < end      mov ax,[bp-8]   ;aspikes  mov ax,[bp-12] ;aspikes  mov a
  5453.                       add WORD PTR [bp-8],2     mov dx,[bp-10]           cwd
  5454.                       cmp ax,[bp-6]   ;end      add WORD PTR [bp-12],2   add a
  5455.                                                 cmp ax,[bp-8]   ;end     adc d
  5456.                                                                          mov c
  5457.                                                                          shl d
  5458.                                                                          add d
  5459.                                                                          mov c
  5460.                                                                          mov b
  5461.                                                                          mov [
  5462.  C                    NEAR                      FAR                      HUGE
  5463.                                                                         mov [
  5464.                                                                          mov [
  5465.                                                                          cmp c
  5466.  
  5467.  diff = (DTYPE)(end-
  5468.          aspikes);    mov ax,[bp-6]   ;end      mov ax,[bp-8]   ;end     pushd
  5469.                       sub ax,[bp-8]   ;aspikes  sub ax,[bp-12]  ;aspikes pusha
  5470.                       sar ax,1                  sar ax,1                 pushW
  5471.                       mov [bp-2],ax   ;diff     mov [bp-2],ax   ;diff    pushW
  5472.                                                                          sar d
  5473.                                                                          rcr a
  5474.                                                                          mov [
  5475.                                                                          mov [
  5476.  
  5477.  
  5478.  
  5479.  Figure 11:  Rearranging Huge Pointers
  5480.  
  5481.  #include <dos.h>
  5482.  
  5483.  char far *
  5484.  huge2far(n)
  5485.  char huge *n;
  5486.       {
  5487.       register unsigned o;
  5488.       register unsigned s;
  5489.       unsigned d;
  5490.       char far *p;
  5491.  
  5492.       o = FP_OFF(n);
  5493.       s = FP_SEG(n);
  5494.       if (o < 0x7ff0) {
  5495.           d = (0x8000 - o) & ~0xf;
  5496.           o += d;
  5497.           s -= (d>>4);
  5498.       }
  5499.       else if (o > 0x8000) {
  5500.           d = (o - 0x7ff0) & ~0xf;
  5501.           o -= d;
  5502.           s += (d>>4);
  5503.       }
  5504.       FP_OFF(p) = o;
  5505.       FP_SEG(p) = s;
  5506.       return p;
  5507.  }
  5508.  
  5509.  ████████████████████████████████████████████████████████████████████████████
  5510.  
  5511.  EMS Support Improves Microsoft Windows 2.0 Application Performance
  5512.  
  5513.  ───────────────────────────────────────────────────────────────────────────
  5514.  Also see the related article:
  5515.    Glossary of Window Memory Terms
  5516.  ───────────────────────────────────────────────────────────────────────────
  5517.  
  5518.  Paul Yao
  5519.  
  5520.  In 1981, IBM introduced its Personal Computer, which, among other things,
  5521.  gave applications developers more memory to work with. You may recall that
  5522.  the best-selling machine at that time was the Apple(R) II, which, with its
  5523.  6502 processor was able to address 64Kb of RAM memory. The IBM(R) Personal
  5524.  Computer was able to address a full megabyte of memory with its Intel(R)
  5525.  8086 processor. Subtracting the memory reserved for the hardware, the
  5526.  program address space of 640Kb was ten times the available memory on the
  5527.  Apple computer. Suddenly, programmers and users found themselves with more
  5528.  memory than they knew what to do with.
  5529.  
  5530.  Eventually, users pushed the limits of 640Kb of memory: software became
  5531.  larger as new features were added, spreadsheets and documents grew as users
  5532.  became more sophisticated, and, with advances in chip technology, the
  5533.  price of memory dropped. What once had seemed an enormous amount of memory
  5534.  was no longer enough.
  5535.  
  5536.  To meet the need for more memory, the Expanded Memory Specification (EMS)
  5537.  was introduced in 1984 to provide applications with memory above the 640Kb
  5538.  limit. EMS defines the software interface for a program to access expanded
  5539.  memory. Using EMS, an application can store up to 8 additional megabytes of
  5540.  data in RAM. AST Research Inc. developed a superset of EMS, called the
  5541.  Enhanced Expanded Memory Specification (EEMS), which provided even greater
  5542.  flexibility in the way that expanded memory was mapped for use by
  5543.  applications.
  5544.  
  5545.  The EMS and EEMS specifications were superseded by the Lotus/Intel/
  5546.  Microsoft Expanded Memory Specification Version 4.0 (LIM EMS 4.0) in August
  5547.  1987. EMS 4.0 provides up to 32Mb of expanded memory, four times the 8Mb
  5548.  supported under EMS 3.2. By consolidating two similar, but distinct, memory
  5549.  specifications, LIM EMS 4.0 simplifies expanded memory support for Windows
  5550.  since any EMS or EEMS card can be made compatible with LIM EMS 4.0 by adding
  5551.  a device driver. Besides unifying the expanded memory interface, EMS 4.0
  5552.  provides multitasking support for expanded memory.
  5553.  
  5554.  This article looks at how expanded memory works, how Windows 2.0 uses
  5555.  expanded memory, how virtual memory is implemented in Windows/386, and how
  5556.  expanded memory support will affect your Windows programming.
  5557.  
  5558.  
  5559.  Expanded Memory
  5560.  
  5561.  EMS 4.0 allows applications to go beyond the 640Kb limit by setting aside
  5562.  part of the 8088's 1Mb address space as a porthole into expanded memory,
  5563.  called a page frame. Depending on how system memory is used, page frames
  5564.  range in size from 16Kb to 1024Kb.
  5565.  
  5566.  Page frames are divided into physical pages, which are typically 16Kb.
  5567.  Expanded memory is divided into logical pages, also 16Kb in size.
  5568.  Applications obtain expanded memory by asking the Expanded Memory
  5569.  Manager (EMM) to allocate a certain number of logical pages. The application
  5570.  must ask the EMM to map a set of logical pages into the physical address
  5571.  space of the hardware in order to use them. The application then accesses
  5572.  expanded memory just as it would access conventional memory. To use another
  5573.  set of logical pages, the application asks the EMM to map in a new set of
  5574.  pages. An application cannot access a page when that page is not mapped in.
  5575.  While the physical address space of the 8088 is 1Mb, expanded memory can
  5576.  have up to 32Mb.
  5577.  
  5578.  Figure 1 is a memory map showing the relationship of an MS-DOS(R)
  5579.  application, such as Lotus(R) 1-2-3(R), to expanded memory logical pages. In
  5580.  this example, there are two slots in the physical address space, labeled
  5581.  Slot-1 and Slot-2. The two logical pages, which are labeled Page-3 and Page-
  5582.  6, are currently mapped into the two slots. Note that the shaded areas are
  5583.  reserved for the operating system and for hardware use (where display
  5584.  memory is located). The single page frame resides above 640Kb, although EMS
  5585.  4.0 allows expanded memory to be mapped into any portion of the physical
  5586.  address space.
  5587.  
  5588.  Expanded memory requires special hardware and special software. The hardware
  5589.  consists of a card that contains memory chips. The software is a device
  5590.  driver, the EMM, which processes the requests for expanded memory actions
  5591.  and makes the expanded memory board perform its mapping magic.
  5592.  
  5593.  Applications must allocate memory by calling the EMM, and by another call
  5594.  that causes the allocated page to be made accessible by hardware mapping.
  5595.  The third task that applications must perform is to free allocated expanded
  5596.  memory once it is no longer needed. These three steps are analogous to the
  5597.  way that memory is allocated from the Windows memory manager: memory is
  5598.  allocated (either with GlobalAlloc or LocalAlloc), it is mapped into a
  5599.  physical address (via GlobalLock or LocalLock), and it is released when no
  5600.  longer needed (by GlobalFree or LocalFree). The fourth step, unlocking
  5601.  memory (with GlobalUnlock or LocalUnlock), is analogous to the unmapping of
  5602.  expanded memory pages, which in EMS is part of the mapping call.
  5603.  
  5604.  Some EMS boards allow only a single 64Kb page frame, which must physically
  5605.  appear above 640Kb. Other boards permit multiple page frames with no limit
  5606.  on the size or location of the page frames. Even though an EMS 4.0 driver
  5607.  can be written for either type of board, their capabilities require that
  5608.  Windows deal with each a little differently. Older boards leave the memory
  5609.  below 640Kb untouched and provide a relatively small page frame. Newer
  5610.  boards can be configured to take over everything above 256Kb, allowing total
  5611.  bank space to exceed 600Kb.
  5612.  
  5613.  As I will discuss later, the size of the available page frame affects how
  5614.  the Windows memory manager uses expanded memory. I will refer to old-style
  5615.  EMS support, which allows only a single page frame, as "small frame" EMS and
  5616.  to the new-style EMS support, which permits multiple page frames, as "large
  5617.  frame" EMS. Note that the presence of terminate-and-stay-resident programs,
  5618.  network device cards, and other dedicated memory applications, may cause
  5619.  Windows to use a newer EMS card in "small frame" EMS mode.
  5620.  
  5621.  The division between the bankable and the nonbankable portions of a given
  5622.  EMS memory configuration is called the "bank line." Knowing where objects
  5623.  will be allocated ("above the bank line" or "below the bank line") will not
  5624.  affect you if you are writing a single, standalone Windows application.
  5625.  But if you develop applications that "talk" to each other, write your own
  5626.  dynamic-link library, use Windows hooks, or wish to use the clipboard or
  5627.  DDE, you should familiarize yourself with the location of memory objects
  5628.  relative to the EMS bank line.
  5629.  
  5630.  The mapping and unmapping of logical expanded memory pages does not require
  5631.  copying large blocks of data. Instead, it involves modifying a few mapping
  5632.  registers. Once these mapping registers are changed, memory mapping
  5633.  automatically occurs on the expanded memory card and is thus fast and
  5634.  efficient, with little performance degradation.
  5635.  
  5636.  
  5637.  Expanded Memory and Windows
  5638.  
  5639.  Windows Version 2.0 uses expanded memory to bank Windows applications. This
  5640.  expanded memory support lets multiple large Windows applications, such as
  5641.  Microsoft Excel and Aldus PageMaker(R), run simultaneously and as fast as if
  5642.  each were running in Windows by itself. Windows 1.x uses expanded memory to
  5643.  swap typical MS-DOS applications, but not for swapping Windows applications.
  5644.  Windows 2.0 will support these older applications, but with the advantage of
  5645.  banking Windows applications in expanded memory.
  5646.  
  5647.  Memory is the most severely constrained resource in Windows. There are two
  5648.  reasons for this: one due to the hardware, and the other related to the
  5649.  multitasking nature of Windows. The hardware constraint is simply that the
  5650.  address space of the 8086 family of processors is 1Mb. The multitasking
  5651.  nature of Windows means that the demand on memory is potentially
  5652.  unlimited, as more and more applications are run. This problem is
  5653.  partially solved in the design of Windows, which provides a type of virtual
  5654.  memory support. Unnecessary code and resource segments marked
  5655.  "discardable" and which are "least recently used," can be destroyed on
  5656.  demand, freeing up memory. Of course, when the destroyed objects are needed
  5657.  again, Windows will re-read them from the disk (code and resource
  5658.  segments in memory are "read-only," which means that the disk image is
  5659.  guaranteed to be correct). The memory problem is not entirely solved by
  5660.  discarding. Each application has a minimum memory requirement, so that
  5661.  two or more large applications may not be able to run simultaneously. This
  5662.  is a problem because users will most likely want to use the large, powerful
  5663.  applications together, to cut and paste data, initiate DDE conversations,
  5664.  and in general to take advantage of Windows' ability to move quickly between
  5665.  applications.
  5666.  
  5667.  Windows 2.0 solves the problem with its enhanced memory manager, which will
  5668.  bank each application into its own set of expanded memory pages, so that
  5669.  each application is, in effect, running on the machine by itself. This works
  5670.  as long as an EMS card and an EMS 4.0 driver are present. The banking of
  5671.  application code and data is entirely transparent to Windows programs. The
  5672.  Windows memory manager performs all required handshaking with the EMM to
  5673.  allocate memory, map and unmap pages, and to free EMS memory when an
  5674.  application is closed.
  5675.  
  5676.  The memory manager could have been enhanced to page in EMS memory so as to
  5677.  give a single Windows application the full 32Mb of memory that EMS 4.0
  5678.  supports. The current design was chosen, however, for implementation
  5679.  considerations, performance goals, and the availability of alternative
  5680.  solutions.
  5681.  
  5682.  One implementation consideration in favor of this design is the need for
  5683.  Windows 2.0 to be backwards compatible with Windows 1.x applications. This
  5684.  means that the interface to the memory manager must be the same. To make use
  5685.  of EMS memory, a new interface would be required. However, this interface
  5686.  already exists──in the form of the EMS 4.0 specification. Therefore, if an
  5687.  application has a need for megabytes of memory, it can talk directly to
  5688.  the EMS memory manager.
  5689.  
  5690.  The second reason for the current design is to meet the main performance
  5691.  goal for supporting expanded memory: allowing several large Windows
  5692.  applications to run simultaneously, with minimum overhead incurred at
  5693.  context-switching time. The current implementation insists only that each
  5694.  application be able to run with acceptable performance in 640Kb. After all,
  5695.  not every computer will have expanded memory, and thus Windows' developers
  5696.  cannot assume its existence, except, perhaps, in vertical-market packages
  5697.  where a turnkey system was being developed.
  5698.  
  5699.  The third reason that Windows EMS support does not provide megabytes of
  5700.  expanded memory for applications is that a disk cache serves the same
  5701.  purpose. Code, resource, and other discardable segments can be destroyed
  5702.  and quickly re-read from the disk cache using the loading and discarding
  5703.  capabilities of the memory manager. Windows 2.0 includes a disk cacher,
  5704.  which dynamically allocates and frees EMS pages as needed. Unlike a RAM
  5705.  drive, optimum performance of the disk cache does not depend on a user
  5706.  decision.
  5707.  
  5708.  Windows 2.0, unlike the earlier versions, will run in the compatibility box
  5709.  of OS/2. Even in this mode, Windows will use expanded memory.
  5710.  
  5711.  
  5712.  How EMS Support Works
  5713.  
  5714.  The Windows memory manager is responsible for controlling the dynamic
  5715.  allocation of memory from the global heap, as well as allocation from
  5716.  each task or library's local heap. Here, we are only interested in memory
  5717.  allocated from the global heap. The Windows memory manager arbitrates
  5718.  requests for blocks of memory, handling all the adminstrative overhead when
  5719.  blocks must be moved or discarded to satisfy allocation requests. When a
  5720.  block of memory is freed, that is, returned to the available pool of
  5721.  memory, the Windows memory manager makes a note of this.
  5722.  
  5723.  Windows pages in a fresh bank of EMS memory for every application that is
  5724.  loaded. Figure 2 is a memory map showing three Windows applications; Write,
  5725.  PageMaker, and Microsoft Excel, each in its own bank of expanded memory
  5726.  pages. In Figure 2, the Microsoft Excel pages are all currently mapped into
  5727.  physical addresses. Note that only the minimum number of expanded memory
  5728.  logical pages are allocated for a given application, so that expanded
  5729.  memory can be used as efficiently as possible.
  5730.  
  5731.  Figure 2 shows that all of the pages in Microsoft Excel are mapped in. This
  5732.  happens whenever Microsoft Excel is processing, or, in Windows terms,
  5733.  whenever Microsoft Excel receives a message via the call to GetMessage. If
  5734.  the user clicks on the PageMaker window, a context switch makes PageMaker
  5735.  the active application. During context switching, the memory manager
  5736.  will bank in all of the PageMaker expanded memory pages, causing all of the
  5737.  Microsoft Excel pages to be banked out. This has implications for sharing
  5738.  memory among applications, which will be discussed later.
  5739.  
  5740.  While the application is processing, the allocation of memory banks
  5741.  remains fixed. This means that Windows applications are still running in
  5742.  640Kb (actually up to 256Kb more, or 896Kb, with a 64Kb EGA card installed),
  5743.  and should be written to obtain satisfactory performance within this
  5744.  memory.
  5745.  
  5746.  The only objects that will be discarded are those that appear in the
  5747.  physical address space, that is, in either the currently mapped EMS memory
  5748.  or below the bank line. Memory objects that are banked out are never
  5749.  discarded.
  5750.  
  5751.  
  5752.  Memory Protection
  5753.  
  5754.  When an application's EMS memory is not mapped into the physical address
  5755.  space, it is protected from other "misbehaving" applications. When Windows
  5756.  is running with large frame EMS, that is, when expanded memory pages can be
  5757.  mapped into any part of the physical address space, each application is
  5758.  completely protected from others. When small frame EMS is running, only
  5759.  those code segments that are in EMS memory are protected. Although this
  5760.  memory protection is not as complete as that of OS/2, in which illegal
  5761.  addressing results in a general protection fault, it does reduce the
  5762.  damage in a way that Windows 1.x did not.
  5763.  
  5764.  
  5765.  Memory Managers
  5766.  
  5767.  While Windows is running with EMS memory, there are really two memory
  5768.  managers present: the Windows memory manager and the EMM. The Windows memory
  5769.  manager is the primary memory manager under Windows 2.0, calling on the EMM
  5770.  only to control expanded memory. Its task is to allocate, map and unmap, and
  5771.  free expanded memory, plus keep track of the EMS registers. Because there
  5772.  are two separate memory managers, a Windows application can call the EMM
  5773.  directly once it knows that EMS is present, to allocate memory on its own.
  5774.  
  5775.  One thing you'll want to know is which objects are allocated above the bank
  5776.  line and are therefore bankable, and which are allocated below the bank line
  5777.  and are thus shareable. This discussion is complicated by the fact that EMS
  5778.  4.0 supports two types of page frames: small and large. The small page frame
  5779.  for EMS memory is 64Kb, while the large page frame for EMS memory can be up
  5780.  to 896Kb, given the proper hardware setup. Because almost the entire
  5781.  address space above 256Kb can be banked by using the large page frame, it
  5782.  offers the most flexibility to the Windows memory manager. Figure 3 shows
  5783.  how expanded memory is used for both types of page frames.
  5784.  
  5785.  More than one instance of a given application, in the current
  5786.  implementation, will share a given bank (although each instance will have
  5787.  its own data segment), thus preventing any problems in the sharing of
  5788.  instance data, either explicitly through GetInstanceData or implicitly
  5789.  through sharing memory handles. Future versions may offer developers the
  5790.  option of requesting a separate EMS bank for each instance of an
  5791.  application.
  5792.  
  5793.  Certainly large frame gives the memory manager the most flexibility, but
  5794.  even with small frame there are significant advantages to EMS support. For
  5795.  small applications, all of the code and resources will fit into banked
  5796.  memory, allowing many small applications to run with little performance
  5797.  degradation. Applications like the control panel, the calculator, or the
  5798.  calendar can be loaded together with a large application such as Microsoft
  5799.  Excel with a minimum of interference. Small frame EMS support, then, allows
  5800.  the user to take better advantage of the multitasking abilities in Windows.
  5801.  
  5802.  
  5803.  Dynamic-Link Libraries
  5804.  
  5805.  When small frame EMS is present, only the resources and code segments from a
  5806.  task are loaded into the EMS banks. With large frame EMS the memory manager
  5807.  places several other types of objects above the line in the EMS banks. These
  5808.  include task data segments, library discardable code segments, and memory
  5809.  allocated with GlobalAlloc. These last two deserve some attention because
  5810.  they raise important implementation issues.
  5811.  
  5812.  Dynamic-link libraries are accessible from any task. Examples of dynamic-
  5813.  link libraries under Windows include Kernel, User, and GDI, as well as all
  5814.  device drivers (keyboard, mouse, timer, display, and printer). Clearly,
  5815.  library routines, such as the GDI routine TextOut, must be accessible from
  5816.  any task at any time. When large frame EMS is present, the code in dynamic-
  5817.  link libraries is handled in one of two ways: fixed code segments live below
  5818.  the line, discardable code segments live above the line.
  5819.  
  5820.  Large frame EMS support places library discardable segments above the line
  5821.  in the bank of the calling task, creating a possibility that at any time a
  5822.  given library code segment may appear in multiple banks. For example,
  5823.  multiple copies of the dialog box control manager, which contains the window
  5824.  procedures for the edit, push button, and other dialog box controls, might
  5825.  concurrently reside in EMS memory. Although there is redundancy, the net
  5826.  result is that applications have access to the library segments that they
  5827.  need, and little room is taken up in the scarce portion of memory below the
  5828.  bank line.
  5829.  
  5830.  Ordinarily, when dynamic-link libraries allocate memory with GlobalAlloc and
  5831.  a large EMS frame is present, the allocation occurs above the line. There
  5832.  are times, however, when it is necessary for a library to have its global
  5833.  memory below the line. Some libraries may have global memory objects that
  5834.  they need to access at all times. In such a case, the GMEM_NOT_BANKED flag
  5835.  is used in the GlobalAlloc call.
  5836.  
  5837.  Windows 1.x printer drivers run into compatibility problems because they do
  5838.  not use the GMEM_NOT_BANKED flag. Printer drivers tend to be the only
  5839.  libraries that are loaded via the LoadLibrary routine; other libraries are
  5840.  loaded as "dependent modules" of tasks. (A dependent module is a library
  5841.  that must be present for a task to execute.) Libraries loaded with
  5842.  LoadLibrary allocate global memory below the EMS bank line, while dependent
  5843.  modules, by default, allocate global memory above the bank line.
  5844.  
  5845.  
  5846.  Changes to the API
  5847.  
  5848.  Only a single routine, LimitEMSPages, has been added to the Windows API.
  5849.  This routine allows an application to control the number of EMS pages that
  5850.  Windows allocates on its behalf. It does not, however, limit the number of
  5851.  pages that an application can allocate for itself from the EMS memory
  5852.  manager.
  5853.  
  5854.  When you want to run Windows with EMS memory disabled, put the /n switch
  5855.  after the WIN command at the DOS prompt:
  5856.  
  5857.    C> win /n
  5858.  
  5859.  
  5860.  Virtual Memory
  5861.  
  5862.  The biggest advantage of Windows/386 is its support of standard MS-DOS
  5863.  applications. The current implementation of Windows/386 creates multiple
  5864.  virtual machines (VMs), each of which behaves as an independent 8086.
  5865.  Because each is effectively given its own 8086 machine with 640Kb of address
  5866.  space, applications can run independently without interfering with each
  5867.  other. Also, because of the ability of the Intel 80386 to intercept direct
  5868.  memory mapped video output, each MS-DOS application can run in its own
  5869.  window on the screen.
  5870.  
  5871.  Note, however, that Windows/386 does not give each Windows application its
  5872.  own 640Kb partition; instead, all Windows applications run in the same 640Kb
  5873.  partition. This means that Windows applications do not have the same memory
  5874.  advantages that MS-DOS applications have running under Windows/386.
  5875.  
  5876.  Memory banking support in Windows/386 is the same as EMS support in Windows
  5877.  2.0. Although it works a little differently, because the 386 has hardware
  5878.  support for memory management, the differences will be transparent to the
  5879.  Windows application.
  5880.  
  5881.  
  5882.  Programming Guides
  5883.  
  5884.  The programming guidelines listed here should be no surprise to Windows
  5885.  programmers, who have been told to be good citizens since they started
  5886.  writing their first window procedure. It is useful to reiterate them,
  5887.  however, because Windows EMS support under Windows 2.0 and virtual memory
  5888.  support under Windows/386 require that you follow them strictly. These
  5889.  guidelines will apply to both, but a few are specific to the addition of
  5890.  EMS support and will help you ensure that your applications run in both
  5891.  Windows 2.0 and Windows/386.
  5892.  
  5893.  
  5894.  Efficient Memory Use
  5895.  
  5896.  There are no current plans for Windows memory management to allow Windows
  5897.  applications to allocate past the 1Mb physical address space of the 8086.
  5898.  Instead, Windows performs bank switching of memory, so each application
  5899.  behaves as if it had the entire computer to itself.
  5900.  
  5901.  The Windows memory manager lets you specify that memory allocated from the
  5902.  global heap, when EMS is running, be below the bank line, so that it is
  5903.  always visible from all applications. You do this with the GMEM_NOT_BANKED
  5904.  flag to the GlobalAlloc call. You should regard this as a method of last
  5905.  resort for sharing memory, because memory below the line is very scarce and
  5906.  is only used for the most critical system objects. Unless you have a very
  5907.  compelling reason, do not perform this type of allocation. A good example
  5908.  of a global allocation that must be below the line is the allocation
  5909.  required by a printer driver.
  5910.  
  5911.  
  5912.  Code Structure
  5913.  
  5914.  To optimize the performance of your application with small frame EMS, mark
  5915.  the most important segments as PRELOAD in the module definition (.DEF)
  5916.  file, and list their names at the beginning of the SEGMENTS list.
  5917.  Performance is improved because, when Windows starts loading an application
  5918.  into memory, it starts with the bankable EMS memory. Only after the EMS
  5919.  bank is filled does Windows start using space below the line. Performance
  5920.  improves with small frame EMS because the memory manager will not discard
  5921.  objects from above the bank line.
  5922.  
  5923.  
  5924.  Data Sharing
  5925.  
  5926.  The clipboard, DDE, and libraries are methods of data sharing guaranteed to
  5927.  work in current and future versions of Windows. Other than the clipboard
  5928.  and DDE, you should not share memory by passing global memory handles
  5929.  between applications. Passing long pointers to data objects should also be
  5930.  avoided. You can't be sure that the target data will be banked in when the
  5931.  receiving application is processing. Applications that ignore this
  5932.  guideline will not run under planned versions of Windows 2.0 or
  5933.  Windows/386.
  5934.  
  5935.  Be sure to copy the data from the clipboard into your own data areas before
  5936.  you close and release the clipboard. Windows supports EMS by copying
  5937.  clipboard objects that have been banked out into the currently banked
  5938.  memory. After the clipboard is closed, these objects will be destroyed,
  5939.  rendering them inaccessible to the receiving application.
  5940.  
  5941.  When an application receives a global handle through the clipboard or DDE,
  5942.  it must check the return value to the GlobalLock call. The Windows memory
  5943.  manager copies into the current bank those DDE or clipboard objects that
  5944.  reside in unbanked EMS memory. If the memory manager is not able to perform
  5945.  this copying (such as in a low memory situation), it informs the receiving
  5946.  application by returning null to GlobalLock.
  5947.  
  5948.  
  5949.  Code Sharing
  5950.  
  5951.  One innovation of Windows is its ability to share code. Only a single copy
  5952.  of any given Windows routine is loaded into memory at any given time. It
  5953.  can be accessed, via the dynamic linking mechanism, by several applications.
  5954.  Window procedures are shared among multiple instances of windows and fine-
  5955.  tuned through subclassing. If you write well-behaved Windows applications,
  5956.  code sharing by EMS memory is not affected: since several instances of an
  5957.  application run in the same bank of EMS memory, there is no problem in
  5958.  sharing code, data, or resources.
  5959.  
  5960.  Dynamic-link libraries are one method for sharing code among several
  5961.  cooperating applications. Windows loads library discardable segments above
  5962.  the bank line, so there can be multiple copies of some code segments in EMS
  5963.  memory. This redundancy can be avoided by converting the library into a
  5964.  windowless task and communicating with the calling applications via the
  5965.  various message posting routines (PostMessage and PostAppMessage).
  5966.  
  5967.  You can never execute code that belongs to another task by calling it
  5968.  directly. In Windows routine terms, you can never call GetProcAddress on
  5969.  another task in order to directly call the target code yourself. Although it
  5970.  worked in Windows 1.x, the Windows 2.0 memory manager won't let you do this.
  5971.  Instead, use the SendMessage routine to perform intertask calls.
  5972.  
  5973.  An application can directly call the EMM to allocate EMS memory, provided it
  5974.  follows EMS 3.2 guidelines; it may use the reallocation function (function
  5975.  17) from LIM EMS 4.0. Windows must be notified so that it does not try to
  5976.  use the 64Kb EMS 3.2 page frame; this is achieved by adding the switch
  5977.  -LIM32 to the call to the resource compiler. Windows avoids putting code in
  5978.  the 64Kb EMS 3.2 window for your application, but it can still use this
  5979.  page frame to bank other applications.
  5980.  
  5981.  
  5982.  Global Memory
  5983.  
  5984.  You should not allocate global memory below the line, that is, you should
  5985.  not use the GMEM_NOT_BANKED switch to GlobalAlloc except as a final resort.
  5986.  When global memory objects are shared among applications, the call to
  5987.  GlobalLock takes care of copying the appropriate blocks of memory, as
  5988.  necessary. Of course, this should only be used with the clipboard and DDE
  5989.  methods to guarantee compatibility with future versions of Windows 2.0,
  5990.  most notably, Windows/386.
  5991.  
  5992.  Your program should always check for failure in allocating memory,
  5993.  reallocating memory, and locking memory. This is critical when using DDE or
  5994.  the clipboard to share data, and it is generally a good Windows programming
  5995.  practice. An invalid far pointer can be deadly, since it allows data to be
  5996.  written or read from anywhere in the computer's address space.
  5997.  
  5998.  
  5999.  Windows Hooks
  6000.  
  6001.  Windows hooks allow you to trap certain types of events before they are
  6002.  placed into the system queue. For example, you can write a keyboard hook to
  6003.  watch for the <Alt>-P key, which may cause a background screen capture
  6004.  program to print the screen on the default printer.
  6005.  
  6006.  The impact of EMS memory on Windows hooks requires some extra programming
  6007.  effort on your part. First of all, Windows hooks must appear below the bank
  6008.  line and should be placed in fixed library seg-ments. Also, because a
  6009.  window hook has no way of knowing whether the initiating task is currently
  6010.  banked in, it must communicate with the initiating task via messages (most
  6011.  likely SendMessage).
  6012.  
  6013.  
  6014.  Debugging Support
  6015.  
  6016.  You can now use CodeView(R), Microsoft's premier debugging tool, to debug
  6017.  Windows 2.0 applications. This is a welcome improvement over Symdeb, a
  6018.  powerful but cryptic debugging tool that appeals mostly to assembly language
  6019.  programmers. CodeView requires either a monochrome monitor or a dumb
  6020.  terminal, because when Windows is running it takes over the screen. Another
  6021.  hardware requirement for using CodeView is that you must have EMS memory
  6022.  installed since CodeView talks directly to the expanded memory manager to
  6023.  minimize the amount of memory taken from the program being debugged.
  6024.  
  6025.  To facilitate debugging when EMS is present, you may want to place the
  6026.  following switch in your WIN.INI file:
  6027.  
  6028.    [kernel]
  6029.    EnableEMSDebug=1
  6030.  
  6031.  which allows you to set breakpoints within discardable library code. Recall
  6032.  that there can be multiple copies of discardable library code segments,
  6033.  each in a different EMS bank. This switch tells the memory manager to update
  6034.  the debugger's table of library discardable segments during every context
  6035.  switch. Any debugger that works with Windows, including Symdeb, CodeView,
  6036.  Answer, or Atron will work properly with EMS.
  6037.  
  6038.  
  6039.  Conclusion
  6040.  
  6041.  For the most part, expanded memory support under Windows will be
  6042.  transparent to the programmer. With the exception of a single new Windows
  6043.  routine, LimitEMSPages, a few flags, and resource compiler switches, the
  6044.  interface that Windows programmers work with has not changed. What has
  6045.  changed, however, is that there must be stricter adherence to interactions
  6046.  between tasks. Data sharing, for example, must be performed with one of the
  6047.  three supported methods: the clipboard, DDE, or via a shared dynamic-link
  6048.  library. Directly calling into another task's code, which is never a good
  6049.  idea, is not possible with the Windows 2.0 memory manager.
  6050.  
  6051.  Programmers will benefit most from the improved performance of Windows 2.0
  6052.  when running several large applications, which will make products that
  6053.  interact with applications like Microsoft Excel and PageMaker more
  6054.  attractive to users. Windows programmers can also, of course, write their
  6055.  own large, powerful applications. Because the EMS support is transparent to
  6056.  the Windows programmer, he or she can use the existing programming
  6057.  interface and let the Windows memory manager take advantage of EMS.
  6058.  
  6059.  
  6060.  Figure 1:  This memory map shows an MS-DOS application such as Lotus 1-2-3
  6061.             using EMS 3.2 expanded memory.
  6062.  
  6063.                     Physical Address            Logical Pages of
  6064.                          Space                  Expanded Memory
  6065.  
  6066.              1024Kb┌─────────────────┐        ┌─────────────────┐
  6067.                    │   System ROM    │        │       L-0       │
  6068.               960Kb├─────────────────┤        ├─────────────────┤
  6069.                    │     Unused      │        │       L-1       │
  6070.   ┌──────────┬832Kb├─────────────────┤        ├─────────────────┤
  6071.   │EMS 3.2   │     │       P-0       ├────┐   │       L-2       │
  6072.   │Page Frame│     ├─────────────────┤    │ ┌─├─────────────────┤
  6073.   │made up of│     │       P-1       ├──┐ └│ │       L-3       │
  6074.   │four 16Kb │     ├─────────────────┤  │   └─├─────────────────┤
  6075.   │physical  │     │       P-2       │  │     │       L-4       │
  6076.   │pages.    │     ├─────────────────┤  │     ├─────────────────┤
  6077.   │          │     │       P-3       │  │     │       L-5       │
  6078.   └──────────┴786Kb├─────────────────┤  │   ┌─├─────────────────┤
  6079.                    │   EGA Memory    │  └──│ │       L-6       │
  6080.               640Kb├─────────────────┤      └─├─────────────────┤
  6081.                    │                 │        │       L-7       │
  6082.                    │                 │        ├─────────────────┤
  6083.                    │     MS-DOS      │        │       L-8       │
  6084.                    │   Application   │        ├─────────────────┤
  6085.                    │     Program     │        │       L-9       │
  6086.                    │                 │        ├─────────────────┤
  6087.                    ├─────────────────┤        │      L-10       │
  6088.                    │ Expanded Memory │        ├─────────────────┤
  6089.                    │     Manager     │        │      L-11       │
  6090.                    ├─────────────────┤        ├─────────────────┤
  6091.                    │     MS-DOS      │        │      L-12       │
  6092.                    └─────────────────┘        ├─────────────────┤
  6093.                                               │      L-13       │
  6094.                                               ├─────────────────┤─┐16Kb
  6095.                                               │      L-14       │ │logical
  6096.                                               ├─────────────────┤─┘page
  6097.                                               │      L-15       │
  6098.                                               ├─────────────────┤
  6099.                                               │        ∙        │
  6100.                                               │        ∙        │
  6101.                                               │        ∙        │
  6102.                                               └─────────────────┘
  6103.  
  6104.  
  6105.  Figure 2:  This sample memory map shows Write, Pagemaker, and Microsoft
  6106.             Excel code and data segments loaded into EMS memory. The Micro-
  6107.             soft Excel segments are currently mapped into the physical
  6108.             address space of the CPU.
  6109.  
  6110.  ┌────────────┐       ┌──────────────┐         ┌───────────────────┐
  6111.  │  Banked    ├───────┤  System ROM  │         │  Write            │
  6112.  └────────────┘ 960Kb └──────────────┘         ├───────────────────┤
  6113.  ┌────────────┐        ──────────────          │  Write            │
  6114.  │  12-16Kb   │        ──────────────          ├───────────────────┤
  6115.  │  physical  ├─────── ──────────────          │  Write            │
  6116.  │  pages     │        ────────────── ┐        ├───────────────────┤
  6117.  └────────────┘        ────────────── ├────┐   │  PageMaker        │
  6118.  ┌────────────┐ 768Kb  ┌─────────────┐┘    │   ├───────────────────┤
  6119.  │  Not       ├────────┤  EGA        │     │   │  PageMaker        │
  6120.  │  Banked    │        │  Memory     │     │   ├───────────────────┤
  6121.  └────────────┘ 640Kb  └─────────────┘     │   │  PageMaker        │
  6122.  ┌────────────┐        ──────────────      │ ┌─├───────────────────┤
  6123.  │  24-16Kb   │        ──────────────      │ │ │  Microsoft Excel  │
  6124.  │  physical  ├─────── ──────────────      │ │ ├───────────────────┤
  6125.  │  pages     │        ────────────── ┐    └│ │  Microsoft Excel  │
  6126.  └────────────┘        ────────────── ├──┐   │ ├───────────────────┤
  6127.  ─ Bank Line ─  256Kb  ┌─────────────┐┘  │   │ │  Microsoft Excel  │
  6128.  ┌────────────┐        │             │   │   └─├───────────────────┤
  6129.  │            │        │             │   │     │  Write            │
  6130.  │            │        │  Windows    │   │     ├───────────────────┤
  6131.  │            │        │  Shareable  │   │     │  Write            │
  6132.  │            │        │  Memory     │   │     ├───────────────────┤
  6133.  │            │        │             │   │     │  PageMaker        │
  6134.  │  Not       │        │             │   │     ├───────────────────┤
  6135.  │  Banked    │        ├─────────────┤   │     │  PageMaker        │
  6136.  │            ├────────│  Windows    │   │     ├───────────────────┤
  6137.  │            │        ├─────────────┤   │     │  PageMaker        │
  6138.  │            │        │  Expanded   │   │   ┌─├───────────────────┤─┐16Kb
  6139.  │            │        │  Memory     │   │   │ │  Microsoft Excel  │ ├logical
  6140.  │            │        │  Manager    │   │   │ ├───────────────────┤─┘page
  6141.  │            │        │             │   └──│ │  Microsoft Excel  │
  6142.  │            │        ├─────────────┤       │ ├───────────────────┤
  6143.  │            │        │  MS-DOS     │       │ │  Microsoft Excel  │
  6144.  └────────────┘        └─────────────┘       └─└───────────────────┘
  6145.                          Physical                Logical Pages of
  6146.                          Address                 Expanded Memory
  6147.                          Space
  6148.  
  6149.  
  6150.  Figure 3:  Expanded Memory is Used for Both Types of Page Frames
  6151.  
  6152.                                  Always               Above
  6153.                                 below the         ┌──the line───┐
  6154.                                   line            Small     Large
  6155.                                                   Frame     Frame
  6156.  
  6157.  Task databases                     √
  6158.  Module (EXE) headers               √
  6159.  Library data segments              √
  6160.  Library fixed Code                 √
  6161.  Thunks                             √
  6162.  Library resources                  √
  6163.  Task data segments                                 √         √
  6164.  Task Resources                                     √         √
  6165.  Task data segments                                           √
  6166.  Library discardable Code                                     √
  6167.  Dynamically Allocated Memory
  6168.  (via GlobalAlloc)                                            √
  6169.  
  6170.  
  6171.  ───────────────────────────────────────────────────────────────────────────
  6172.  Glossary of Windows Memory Terms
  6173.  ───────────────────────────────────────────────────────────────────────────
  6174.  
  6175.  Bank line - Also called the EMS swap line, it is the memory address below
  6176.  which memory is not banked and above which memory is banked. When Windows is
  6177.  running with expanded memory, the nonbanked memory is always available,
  6178.  while the banked memory is only available when the task that owns the banks
  6179.  is running.
  6180.  
  6181.  Banked memory - Memory that is made to appear within (mapped into) the
  6182.  physical address space of the 8088 by means of a software driver (the
  6183.  Expanded Memory Manager) working with an expanded memory card.
  6184.  
  6185.  Context switch - The changes that Windows must make when the user turns his
  6186.  attention from one Windows application to another. For example, suppose both
  6187.  WindowsWrite and WindowsPaint are open. If the user has been entering text
  6188.  into WindowsWrite and then clicks the mouse in WindowsPaint, the system
  6189.  performs a context switch. The only thing the user sees is that the caption
  6190.  bar changes color. If the newly active window was covered by another
  6191.  overlapping window in Windows 2.0, it is brought to the top.
  6192.  
  6193.  Discardable memory object - One of the flags that the Windows memory manager
  6194.  sets to indicate various characteristics of the memory object when it
  6195.  allocates memory. When the Windows memory manager is unable to satisfy
  6196.  allocation requests by simply moving memory objects, it will throw away
  6197.  discardable memory objects on a least-recently-used basis.
  6198.  
  6199.  Dynamically allocated memory - The memory that a Windows program directly
  6200.  obtains from the Windows memory manager. In this article, the term is
  6201.  synonymous with globally allocated memory.
  6202.  
  6203.  Enhanced Expanded Memory Specification (EEMS) - An extension to the Expanded
  6204.  Memory Specification that allows any part of the 8088 address space to be
  6205.  banked.
  6206.  
  6207.  Expanded Memory Specification (EMS) - This specification, also known as LIM
  6208.  EMS, defines how a program allocates and uses expanded memory. This consists
  6209.  of calls to the expanded memory device driver (see Expanded Memory
  6210.  Manager).
  6211.  
  6212.  Expanded Memory Manager (EMM) - A device driver that controls expanded
  6213.  memory. The EMS defines how a program communicates with the EMM to allocate,
  6214.  map, and free logical pages of expanded memory.
  6215.  
  6216.  Extended memory - The memory above 1Mb on a 80286- or 80386-based
  6217.  microcomputer. Memory of this type can only be directly addressed when
  6218.  running in protected mode, such as in OS/2. Ordinarily, extended memory is
  6219.  used for RAM disks and print spoolers. Most memory cards can be configured
  6220.  for expanded memory, extended memory, or both.
  6221.  
  6222.  Handle - A token that is returned when an object is created. Handles
  6223.  identify Windows' dynamically allocated memory objects.
  6224.  
  6225.  Logical page - A segment of expanded memory. EMS memory is allocated in
  6226.  logical pages, which are typically 16Kb. In order to use data stored in a
  6227.  logical page, EMS must map the logical page into a physical page.
  6228.  
  6229.  Mapping - The process by which a logical page of expanded memory is made to
  6230.  appear in a physical page, that is, within the address space of the 8088, so
  6231.  that programs can read and write to the memory.
  6232.  
  6233.  Old application support - The ability of Windows to execute standard,
  6234.  character-based MS-DOS applications, such as Microsoft Word, Lotus 1-2-3, or
  6235.  Microrim(R) R:BASE(R) System V.
  6236.  
  6237.  Page frame - A collection of physical pages into which expanded memory
  6238.  logical pages can be made to appear.
  6239.  
  6240.  Physical page - The absolute physical space in the 8088's 1Mb of address
  6241.  space into which EMS logical pages are mapped.
  6242.  
  6243.  Thunk - A small piece of code that sits in a fixed place in memory to
  6244.  intercept the calls to a routine located in a moveable or discardable code
  6245.  segment. Thunks are an important part of the dynamic linking mechanism of
  6246.  Windows, allowing the memory manager to move or discard code segments with
  6247.  relatively little overhead expended towards notifying the prospective
  6248.  callers of a routine that the routine is no longer resident. Subsequent
  6249.  calls cause the thunk to reload the segment containing the routine. Return
  6250.  thunks are created to intercept a return to a discarded code segment. Thunks
  6251.  also perform context switches; this is what the thunk created by
  6252.  MakeProcInstance does to ensure that a dialog box procedure is using the
  6253.  correct data segment.
  6254.  
  6255.  Window - Another term for an EMS page frame.
  6256.  
  6257.  Windows memory manager - The part of Windows that loads code and data
  6258.  segments into memory. The Windows memory manager attempts to satisfy dynamic
  6259.  allocation requests by first moving memory objects; if this is not
  6260.  sufficient, it then discards memory objects that are marked as discardable.
  6261.  The memory manager in Windows 2.0 has been extended to bank-switch memory
  6262.  between Windows applications.
  6263.  
  6264.  ████████████████████████████████████████████████████████████████████████████
  6265.  
  6266.  LIM EMS 4.0: A Definition For the Next Generation of Expanded Memory
  6267.  
  6268.  Marion Hansen and John Driscoll
  6269.  
  6270.  Back in the bad old days, if your spreadsheet program returned a memory-
  6271.  full error, you were out of memory──and out of luck. All that changed in
  6272.  1984, when Lotus Development Corp., Intel Corp., and Microsoft Corp.
  6273.  smashed the 640Kb conventional memory barrier with their Expanded Memory
  6274.  Specification (EMS). The EMS allowed the creation of another kind of
  6275.  memory: expanded memory.
  6276.  
  6277.  Expanded resides on one or more add-in boards in the computer (Intel's
  6278.  Above(TM) Board and AST's Advantage!(TM) are examples). The EMS defines a
  6279.  segment of memory, located within the first 1Mb of memory, as the page
  6280.  frame. The page frame is a window into expanded memory.
  6281.  
  6282.  Just after an application program starts executing, it can allocate a
  6283.  certain number of 16Kb pages of expanded memory for its own use. By
  6284.  mapping pages in and out of the frame, the program can access any area of
  6285.  expanded memory it has allocated for itself.
  6286.  
  6287.  MS-DOS(R) cannot access expanded memory directly; instead, the job of
  6288.  handling the extra memory goes to the Expanded Memory Manager (EMM), which
  6289.  is defined by the EMS. EMM functions can be added to any program to enable
  6290.  it to use expanded memory. (See "Expanded Memory: Writing Programs that
  6291.  Break the 640Kb Barrier," MSJ, March 1987, for full details on expanded
  6292.  memory and how to write programs that use it.)
  6293.  
  6294.  To avoid confusion, remember that EMS is the Expanded Memory
  6295.  Specification, and that EMM is the Expanded Memory Manager. EMS (the spec)
  6296.  defines the EMM (the manager), and the EMM provides the functions that
  6297.  application programs need to use expanded memory.
  6298.  
  6299.  In the fall of 1987 Lotus, Intel, and Microsoft released the latest EMS,
  6300.  Version 4.0. This article describes the enhancements to EMS 4.0 and how to
  6301.  use them for even greater expanded memory performance.
  6302.  
  6303.  
  6304.  New Feature Set
  6305.  
  6306.  Intel sent a free EMM 4.0 upgrade to all registered Above Board users.
  6307.  Except for faster execution of expanded memory tests at power-on, however,
  6308.  users will not see improved performance with their existing programs
  6309.  until they have access to new application programs written specifically to
  6310.  the EMS 4.0 spec. Even so, certain features of EMS 4.0 have generated
  6311.  excitement in the expanded memory world:
  6312.  
  6313.    ■  With AST's endorsement, EMS 4.0 is now the expanded memory standard
  6314.       for the personal computer industry.
  6315.  
  6316.    ■  The 15 new functions defined by EMS 4.0 provide faster performance
  6317.       and more efficient use of memory.
  6318.  
  6319.    ■  EMS 4.0 supports a full 32Mb of expanded memory; earlier versions
  6320.       only supported up to 8Mb.
  6321.  
  6322.  The OS/2 systems don't run on 8088- or 8086-based computers. EMS 4.0
  6323.  extends the useful life of existing MS-DOS-based applications on 8088-,
  6324.  8086-, 80286-, and 80386-based computers.
  6325.  
  6326.    ■  Many software vendors are already updating their programs (such as
  6327.       Lotus(R) 1-2-3(R) Version 3 and Microsoft Windows 2.0) in order  to take
  6328.       advantage of EMS 4.0's new features.
  6329.  
  6330.    ■  Software written for earlier versions of the EMS is compatible with
  6331.       EMS 4.0.
  6332.  
  6333.    ■  EMM 4.0 runs on existing hardware; you don't need new expanded
  6334.       memory boards to install EMM 4.0. However, not all expanded memory
  6335.       boards currently support every enhancement to EMM 4.0. For example,
  6336.       the AST RAMpage!(R) board and the Intel Above Board 2 support placing
  6337.       the page frame in memory below 640Kb, while the Intel Above Board
  6338.       286 currently does not.
  6339.  
  6340.  
  6341.  EMS 4.0 Enhancements
  6342.  
  6343.  The enhancements to EMS 4.0 improve performance and memory efficiency and
  6344.  make it easier to write programs that use expanded memory.
  6345.  
  6346.    ■  With EMM 4.0, one calls maps in all logical pages that can be
  6347.       physically mapped  to one page frame; earlier versions required each
  6348.       page to be mapped separately.
  6349.  
  6350.    ■  New functions allow application programs to dynamically increase
  6351.       and decrease the amount of expanded memory allocated to them. With
  6352.       each program using just the amount of expanded memory it needs,
  6353.       multiple programs can share expanded memory more efficiently.
  6354.  
  6355.    ■  Now you can name handles-the values that the EMM assigns and uses to
  6356.       identify a block of memory requested by an application program-so
  6357.       that data and code can be shared by application programs.
  6358.  
  6359.    ■  EMS 4.0 adds special functions for operating systems (such as MS-
  6360.       DOS) and environments (such as Windows and Desqview) to allow
  6361.       programs  to isolate themselves from all other software in the
  6362.       system, thus  providing a safe environment for programs sharing
  6363.       expanded memory.
  6364.  
  6365.    ■  New EMS functions directly support executing code in expanded memory.
  6366.       Earlier versions supported the execution of code in expanded
  6367.       memory, but implementation was cumbersome. For example, before EMS
  6368.       4.0, terminate-and-stay-resident (TSR) programs required commands to
  6369.       explicitly map the new context into expanded memory when the TSR
  6370.       program was called up and to restore the previous context when the
  6371.       TSR returned control to the application program. Now the job of
  6372.       mapping and restoring contexts can be handled directly with EMS 4.0
  6373.       functions, which allows for easier and more efficient execution of
  6374.       code.
  6375.  
  6376.    ■  Earlier EMS versions put the page frame in an unused 64Kb block of
  6377.       memory between 640Kb and 1Mb. EMS 4.0 supports the page frame
  6378.       anywhere in the first 1Mb of memory. Hardware may limit the
  6379.       location of the page frame-in an 80286-based computer such as an IBM(R)
  6380.       PC/AT(R), for example, the page frame can reside only between 256Kb
  6381.       and 1Mb. For an 80386-based computer or an IBM PS/2(TM) computer
  6382.       with the Micro Channel(TM), on the other hand, the page frame can be
  6383.       anywhere in the first 1Mb of memory.
  6384.  
  6385.    ■  Before EMS 4.0, the page frame held four pages. Now you can define a
  6386.       page frame of up to eight pages in memory above 640Kb. The size of
  6387.       the page frame in memory below 640Kb is limited only by the amount of
  6388.       available memory. (In some implementations of the EMM, you can
  6389.       use all 640Kb for the page frame by putting the EMM itself into
  6390.       expanded memory.)
  6391.  
  6392.    ■  Although the standard size of expanded memory pages is 16Kb, some
  6393.       expanded memory boards use smaller pages. EMS 4.0 supports both
  6394.       16Kb pages and smaller-sized pages.
  6395.  
  6396.  
  6397.  The Page Frame
  6398.  
  6399.  Although EMM 4.0 supports locating a page frame in any available memory
  6400.  between 0Kb and 1Mb, how you plan to use the page frame determines whether
  6401.  you'll place it above or below 640Kb. For application programs, continue
  6402.  to locate the page frame above 640Kb because existing applications may
  6403.  not work when the page frame is located below 640Kb. Locate page frames
  6404.  used by most operating systems and environments in memory below 640Kb.
  6405.  (An exception to this is the Windows/386 EMM, which cannot use a page
  6406.  frame located below 640Kb.) In any case, you cannot create a page frame in
  6407.  memory below 640Kb unless you first create a page frame in memory above
  6408.  640Kb.
  6409.  
  6410.  
  6411.  New EMM Parameters
  6412.  
  6413.  The LIM EMM 4.0 device driver has new parameters, one of which is of
  6414.  special interest to users who are concerned with speed and efficiency.
  6415.  
  6416.  The handle count parameter lets you tell the EMM to support as many
  6417.  handles as a particular application program needs. (A handle is a value
  6418.  that the EMM assigns and uses to identify a block of memory requested by
  6419.  an application program.) The EMM allocates memory based on the number of
  6420.  handles requested by the application. By specifying a small handle count,
  6421.  you can save conventional memory and allow the EMM to run faster.
  6422.  
  6423.  The EMM 4.0 handle count default is 64 handles; programs written for
  6424.  earlier versions use a maximum of 32 handles. If you are using older
  6425.  programs with EMM 4.0, consider changing the EMM handle count to 32 for
  6426.  faster execution. The maximum number of handles is 254.
  6427.  
  6428.  
  6429.  The EMM Functions
  6430.  
  6431.  The EMM functions provide the tools application programs need to use
  6432.  expanded memory. EMS 4.0 doubled the number of EMM functions. Figure 1
  6433.  lists all 30 functions; the new functions are shaded.
  6434.  
  6435.  Functions 16 through 30 are new to EMM 4.0. This section describes each
  6436.  new function. Functions 26, 28, and 30 are for use by operating systems
  6437.  (such as MS-DOS) and environments (Windows and Desqview, for example);
  6438.  the rest are for application programs.
  6439.  
  6440.  
  6441.  Program Functions
  6442.  
  6443.  Get/Set Partial Page Map (Function 16) handles situations Functions 8, 9,
  6444.  and 15 cannot handle. Functions 8 and 9, respectively, handles only four-
  6445.  page page frames and can save and restore expanded memory pages only when
  6446.  the page frame is located between 640Kb and 1Mb. Function 15 saves and
  6447.  restores all the pages (no matter how many pages are in the page frame),
  6448.  even pages located below 640Kb. Function 16 also saves and restores all
  6449.  pages anywhere between 0 and 1Mb, but, unlike Function 15, it lets you
  6450.  choose the pages you want to save or restore.
  6451.  
  6452.  Function 16 has three subfunctions. The Get Partial Page Map subfunction
  6453.  saves a partial mapping context for specific mappable memory regions in a
  6454.  system. This subfunction can be faster than Function 15 because it saves
  6455.  only a subset of the entire mapping context and uses much less memory for
  6456.  the save area. You can use the Get Partial Page Map subfunction with or
  6457.  without a handle, while Function 8 requires a handle.
  6458.  
  6459.  The Set Partial Page Map subfunction restores a partial mapping context
  6460.  for specific mappable memory regions in a system. Like Get Partial Page
  6461.  Map, this subfunction uses far less memory for the save area than Function
  6462.  15, and you can use it with or without a handle.
  6463.  
  6464.  The Get Size of Partial Page Map Save Array subfunction returns the
  6465.  storage requirements for the array passed by the Get/Set Partial Page Map
  6466.  subfunctions. Use this subfunction before the other two
  6467.  subfunctions.
  6468.  
  6469.  Map/Unmap Multiple Handle Pages (Function 17) maps or unmaps (in one
  6470.  invocation) logical pages into as many physical pages as the system
  6471.  supports. This means less overhead than mapping pages one at a time (as
  6472.  required by EMS versions before 4.0). Use this function instead of
  6473.  Function 5 for application programs that do a lot of page mapping.
  6474.  
  6475.  Reallocate Pages (Function 18) allows an application program to
  6476.  dynamically increase or decrease the number of logical pages allocated to
  6477.  an EMM handle, so programs can share expanded memory more efficiently.
  6478.  Function 18 does not determine the number of pages a program needs;
  6479.  rather, the program itself must specify the number of pages to allocate
  6480.  or deallocate (depending on its needs at any one time).
  6481.  
  6482.  Get/Set Handle Attribute (Function 19) defines a handle as volatile or
  6483.  nonvolatile. If it is nonvolatile, the handle, its name (if it has one),
  6484.  and the contents of the pages allocated to it are maintained after a warm
  6485.  boot. For example, this function saves the contents of a RAM disk in
  6486.  expanded memory after a warm boot. Function 19 is not supported by most
  6487.  hardware because it disables memory refresh signals for a long period
  6488.  of time.
  6489.  
  6490.  Function 19 has three subfunctions. The Get Handle Attribute
  6491.  subfunction returns the attribute (volatile or non-volatile) associated
  6492.  with a handle. The Set Handle Attribute subfunction changes the
  6493.  attribute associated with a handle. Before using Function 19's other two
  6494.  subfunctions, you can use the Get Attribute Capability subfunction to
  6495.  determine whether the EMM supports the non-volatile attribute.
  6496.  
  6497.  By naming the handle associated with a block of memory, Get/Set Handle
  6498.  Name (Function 20) lets application programs share the same area in
  6499.  expanded memory associated with a handle. Assigning a name to a handle
  6500.  also protects the memory specified by the handle because only a program
  6501.  that knows the handle name can get that handle and use the memory assigned
  6502.  to it. Function 20 has two subfunctions. The Get Handle Name subfunction
  6503.  gets the eight-character name assigned to a handle. The Set Handle Name
  6504.  subfunction assigns an eight-character name to a handle.
  6505.  
  6506.  Get Handle Directory (Function 21) has three subfunctions. The Get
  6507.  Handle Directory subfunction returns an array which contains all active
  6508.  handles and their associated names (if any).
  6509.  
  6510.  The Search for Named Handle subfunction searches the handle name directory
  6511.  for the specified handle name. If it finds the name, the subfunction then
  6512.  returns the handle number associated with the name. An application
  6513.  program is able to use this subfunction to determine if a shared handle
  6514.  exists.
  6515.  
  6516.  The Get Total Handles subfunction returns the total number of handles
  6517.  that the EMM supports. (Different versions of EMM support different
  6518.  numbers of handles. EMM 4.0 supports up to 254 handles; earlier
  6519.  versions support just 32).
  6520.  
  6521.  Functions 22 and 23 make executing code in expanded memory easier and more
  6522.  efficient. Alter Page Map and Jump (Function 22) changes the memory
  6523.  mapping context and transfers control to the specified address. The
  6524.  original memory mapping context is lost. This function is analogous to
  6525.  the FAR JUMP in the 8086 family architecture.
  6526.  
  6527.  Alter Page Map and Call (Function 23) contains two subfunctions. The Alter
  6528.  Page Map and Call subfunction saves the current memory mapping context,
  6529.  alters the specified memory mapping context, transfers control to the
  6530.  specified address, and restores the state of the specified mapping
  6531.  context after the return. This subfunction is analogous to the FAR CALL
  6532.  in the 8086 family architecture.
  6533.  
  6534.  The Alter Page Map and Call subfunction pushes information onto the stack.
  6535.  The Get Page Map Stack Space Size subfunction returns the number of
  6536.  bytes of stack space the Alter Page Map and Call subfunction needs to do
  6537.  this. Use the information provided by Get Page Map Stack Space Size to
  6538.  alter the stack size before using either Function 22 or the Alter Page Map
  6539.  and Call subfunction.
  6540.  
  6541.  Move/Exchange Memory Region (Function 24) lets you move large amounts of
  6542.  memory without mapping and unmapping pages. You can move or exchange up
  6543.  to 84 pages without mapping any logical pages. Function 24 has two
  6544.  subfunctions. The Move Memory Region subfunction copies and the Exchange
  6545.  Memory Region subfunction exchanges: conventional memory to
  6546.  conventional memory, conventional memory to expanded memory, expanded
  6547.  memory to conventional memory, and expanded memory to expanded memory.
  6548.  The subfunctions maintain the current mapping context, so the program
  6549.  does not have to save and restore it.
  6550.  
  6551.  Get Mappable Physical Address Array (Function 25) tells an application
  6552.  what physical memory is mappable. Function 2 (get page frame address) gives
  6553.  the segment address of the first four pages in the page frame. Use Function
  6554.  25 to find the amount of mappable physical memory for page frames larger
  6555.  than four pages. Function 25 has two subfunctions. The Get Mappable Physical
  6556.  Address Array subfunction returns an array containing the segment address
  6557.  and physical page number for each mappable physical page in a system. The
  6558.  array is a cross reference between physical page numbers and the actual
  6559.  segment addresses for each mappable page in the system. The array is sorted
  6560.  by segment address in ascending order, but the physical page numbers
  6561.  associated with the segment address are not necessarily in ascending order.
  6562.  
  6563.  The Get Mappable Physical Address Array Entries subfunction gets the
  6564.  number of entries needed by the array returned by the Get Mappable
  6565.  Physical Address Array subfunction. Before using the Get Mappable
  6566.  Physical Address Array Entries subfunction, use the Get Mappable
  6567.  Physical Address Array subfunction to determine how much memory to
  6568.  allocate for storing the physical address array.
  6569.  
  6570.  Allocate Raw Pages (Function 27) with two subfunctions allocates the
  6571.  number of raw pages requested by the operating system or environment
  6572.  and assigns a unique EMM handle to these pages. (Some expanded memory
  6573.  boards have pages of less than the standard 16Kb. These non-standard sized
  6574.  memory pages are called raw pages.) Handles assigned to raw pages are
  6575.  called raw handles. Function 4 (Allocate Pages) allocates only 16Kb
  6576.  pages; it does not support raw pages.
  6577.  
  6578.  Programs whose page frames are in conventional memory must use Prepare
  6579.  Expanded Memory Hardware for Warm Boot (Function 29) when they detect
  6580.  Ctrl-Alt-Del. This function prepares the expanded memory hardware for an
  6581.  impending warm boot.
  6582.  
  6583.  
  6584.  Environment Functions
  6585.  
  6586.  The following functions are for inclusion in operating system and
  6587.  environment programs so the operating system/environment knows the kind
  6588.  of hardware support available for expanded memory. Never use Function 26,
  6589.  28, or 30 in application programs.
  6590.  
  6591.  Get Expanded Memory Hardware Information (Function 26) is only for use
  6592.  by operating systems (such as DOS) and environments (such as Windows
  6593.  386 and Desqview). The operating system/environment can disable Function
  6594.  26 at any time. The function has two subfunctions. The Get Hardware
  6595.  Configuration Array subfunction returns an array containing expanded
  6596.  memory hardware configuration information for use by operating systems
  6597.  and environments. The operating system/environment uses this information
  6598.  to determine the hardware support for expanded memory, including raw page
  6599.  size, alternate register sets available, context save area size, and
  6600.  register sets for DMA channels.
  6601.  
  6602.  Some expanded memory boards have pages of less than the standard 16Kb,
  6603.  called raw pages. Function 3 (Get Unallocated Page Count) returns only
  6604.  the number of 16Kb pages. The Get Unallocated Raw Page Count subfunction
  6605.  returns the number of unallocated raw mappable pages and the total number
  6606.  of raw mappable pages in expanded memory to the operating
  6607.  system/environment.
  6608.  
  6609.  Alternate Map Register Set (Function 28) is for use by operating systems
  6610.  and environments only. It has nine subfunctions.
  6611.  
  6612.  The Get Alternate Map Register Set subfunction responds in one of two
  6613.  ways, depending on the setting of the map register set which is active
  6614.  when the function is invoked. If the map register set is equal to zero, a
  6615.  pointer to a context save area is returned. If the map register set is
  6616.  greater than zero, the number of the alternate map register set is
  6617.  returned.
  6618.  
  6619.  The Set Alternate Map Register Set subfunction responds in one of two
  6620.  ways, depending on the map register set specified. If the alternate map
  6621.  register set equals zero, map register set zero is activated, and the
  6622.  contents of the map register context save area is copied into register
  6623.  set zero on each expanded memory board in the system. If the alternate
  6624.  map register set specified is not zero, the alternate map register set
  6625.  specified is activated. The restore area, which the operating system is
  6626.  pointing to, is not used.
  6627.  
  6628.  The Get Alternate Map Save Array Size subfunction returns the storage
  6629.  requirements for the map register context save area that is referenced by
  6630.  the other subfunctions.
  6631.  
  6632.  If an alternate map register is available, the Allocate Alternate Map
  6633.  Register Set subfunction gets its number and copies the currently active
  6634.  alternate map register set's contents into the newly allocated alternate
  6635.  map register set's mapping registers. This does not change the alternate
  6636.  map register set in use but prepares the new alternate map register set
  6637.  for a subsequent Set Alternate Map Register Set subfunction. Operating
  6638.  systems can use this subfunction to quickly switch mapping contexts.
  6639.  
  6640.  The Deallocate Alternate Map Register Set subfunction returns the
  6641.  alternate map register set to the memory manager. The memory manager can
  6642.  then reallocate the alternate map register set. This subfunction also
  6643.  makes the mapping context of the specified alternate map register
  6644.  unavailable for reading or writing, thus protecting the pages previously
  6645.  mapped in an alternate map register set by making them inaccessible. The
  6646.  current alternate map register set cannot be deallocated, so memory
  6647.  currently mapped into conventional and expanded memory is inaccessible.
  6648.  
  6649.  The four DMA subfunctions let operating systems/environments use DMA
  6650.  register sets. (Hardware that supports these subfunctions is not yet
  6651.  available.) If a DMA register set is available, the Allocate DMA Register
  6652.  Set subfunction gets the current number of a DMA register set for an
  6653.  operating system/environment. This subfunction is useful with
  6654.  multitasking operating systems, in which you would like to switch to
  6655.  another task when one task is waiting for DMA to complete. The Deallocate
  6656.  DMA Register Set subfunction deallocates the specified DMA register set.
  6657.  
  6658.  The Enable DMA on Alternate Map Register Set subfunction allows DMA
  6659.  accesses on a specific DMA channel to be associated with a specific
  6660.  alternate map register set. This function is useful in a multitasking
  6661.  operating system, where you would like to switch to another task until
  6662.  the first task's DMA operation completes. The Disable DMA on Alternate
  6663.  Map Register Set subfunction disables DMA accesses for all DMA channels
  6664.  which were associated with a specific alternate map register set.
  6665.  
  6666.  Enable/Disable OS/E Function Set (Function 30) is only used by operating
  6667.  systems and environments. It includes three subfunctions which are
  6668.  Enable OS/E Function Set subfunction that will enable Function 26 (Get
  6669.  Expanded Memory Hardware Information), Function 28 (Alternate Map
  6670.  Register Sets), and also Function 30 (Enable/Disable Operating System
  6671.  Functions).
  6672.  
  6673.  The Disable OS/E Function Set subfunction disables Functions 26, 28, and
  6674.  30.
  6675.  
  6676.  The Return Access Key subfunction lets the operating system/environment
  6677.  return the access key to the EMM. Returning the access key to the EMM
  6678.  enables access to the operating system/environment function set.
  6679.  
  6680.  
  6681.  Example Programs
  6682.  
  6683.  The following two examples (written in Microsoft(R) C, Version 5.0, and
  6684.  Microsoft MASM, Version 5.0) illustrate how functions new to EMS 4.0 can
  6685.  be implemented. The first example shows how expanded memory can be shared
  6686.  between two application programs. The first program (SAVSCR.EXE) saves
  6687.  the current screen, initializes a blank screen in expanded memory, and
  6688.  then exits (see Figure 2). The second program (SWPSCR.EXE) saves its
  6689.  current screen, displays the blank screen and the first program's current
  6690.  screen, and then restores its own current screen.
  6691.  
  6692.  Before it can use expanded memory, SAVSCR.EXE must first see if the EMM is
  6693.  present (by getting the device name from the device driver header).
  6694.  Because SAVSCR.EXE uses 4.0 functions, it also checks the EMM version
  6695.  (Function 7), and exits if EMM is earlier than version 4.0 (see Figure 3).
  6696.  
  6697.  SAVSCR.EXE next determines the number of unallocated pages in expanded
  6698.  memory (Function 3) and exits if there are fewer pages than it needs.
  6699.  Then the program allocates the expanded memory pages it needs and gets an
  6700.  EMM handle (Function 4), assigning the unique name SHARED to the handle
  6701.  (Function 20). This unique handle name will be used later by the second
  6702.  application program to find the handle so it can use the same expanded
  6703.  memory (see Figure 4).
  6704.  
  6705.  Next, SAVSCR.EXE uses the Map/Unmap Multiple Handle function (Function 17)
  6706.  to map the needed pages into the page frame. This is done with one
  6707.  function call; earlier versions of the EMM require one call for each page
  6708.  (see Figure 5).
  6709.  
  6710.  Then the SAVSCR.EXE program copies the current screen to logical page 0
  6711.  and physical page 0, using the Move Memory Region subfunction (Function
  6712.  24). Although this particular move does not span any more than one
  6713.  expanded memory page, the same function call can transfer up to 1Mb of
  6714.  memory without mapping in any logical pages of expanded memory (see
  6715.  Figure 6).
  6716.  
  6717.  Finally, SAVSCR.EXE unmaps all of the pages using the Map/Unmap Multiple
  6718.  Handle Pages function (Function 17) and then exits without returning the
  6719.  handle to the EMM (see Figure 5).
  6720.  
  6721.  Expanded memory is now protected. The second program, SWPSCR.EXE, can find
  6722.  this same expanded memory by using the Search for Named Handle
  6723.  subfunction.
  6724.  
  6725.  First, SWPSCR.EXE verifies that the EMM is present and is version 4.0 or
  6726.  greater, as the first program did. However, unlike the SAVSCR.EXE
  6727.  program, SWPSCR.EXE does not need to allocate pages because it can use the
  6728.  same handle named SHARED that SAVSCR.EXE received.
  6729.  
  6730.  SWPSCR.EXE uses the Search for Named Handle subfunction (Function 21) to
  6731.  determine if the handle named SHARED is present. If the first program
  6732.  (SAVSCR.EXE), has not been executed already, SWPSCR.EXE will exit,
  6733.  because it could not find the handle named SHARED.
  6734.  
  6735.  Finally, SWPSCR.EXE uses the Exchange Memory Region subfunction (Function
  6736.  24) to: swap its current screen with the logical page that has the blank
  6737.  screen; swap the blank screen with the logical page that has the
  6738.  original screen; and then restore its own current screen. (Because the
  6739.  Exchange Memory Region subfunction handles all mapping, none of the
  6740.  logical pages have to be mapped before this call.)
  6741.  
  6742.  SWPSCR.EXE unmaps all expanded memory pages be-fore exiting (Function 17)
  6743.  as seen in Figure 7.
  6744.  
  6745.  SWPSCR.EXE illustrates how EMM 4.0 has simplified executing code in
  6746.  expanded memory. (For an idea of just how much EMM 4.0 has accomplished
  6747.  in this area, refer to the KERNEL module in "Expanded Memory: Writing
  6748.  Programs That Break the 640Kb Barrier," MSJ, March 1987.)
  6749.  
  6750.  As in the first example, MAPCALL.EXE (see Figure 8) checks for the
  6751.  version of EMM and allocates expanded memory. Then it uses the MS-DOS
  6752.  Load Overlay function to load two modules (MODULE1.EXE and MODULE2.EXE)
  6753.  into expanded memory (see Figure 9).
  6754.  
  6755.  Next, MAPCALL.EXE uses the Alter Page Map and Call subfunction (Function
  6756.  23) to execute MODULE1.EXE (see Figure 10).
  6757.  
  6758.  MODULE1.EXE in turn uses the Search for Named Handle subfunction to
  6759.  determine which handle to use and uses the Alter Map Page and Call
  6760.  subfunction to execute MODULE2.EXE (see Figure 11).
  6761.  
  6762.  MODULE2.EXE prints a message to the screen and does a far return. The far
  6763.  return causes the EMM to restore the map-ping specified in the old map
  6764.  page definition and return control to MODULE1.EXE (see Figure 12).
  6765.  
  6766.  MODULE1.EXE then does a far return, which causes the EMM to return control
  6767.  to MAPCALL.EXE and to restore the original mapping context. MAPCALL.EXE
  6768.  then releases its handle and exits to MS-DOS.
  6769.  
  6770.  
  6771.  To Get EMS 4.0
  6772.  
  6773.  If you are interested in developing application programs that use
  6774.  expanded memory, call Intel for a free copy of the Lotus/Intel/Microsoft
  6775.  Expanded Memory Specification. From the United States and Canada, call
  6776.  (800) 538-3373. Outside the United States and Canada, call (503) 629-7354.
  6777.  
  6778.  
  6779.  Figure 1:  EMM 4.0 FUNCTIONS
  6780.  
  6781. ╓┌──────────────────────────┌────┌───────────────────────────────────────────╖
  6782.  Function Name              No.  Description
  6783.  
  6784.  GET STATUS                 1    Returns a status code to tell you whether
  6785.  Function Name              No.  Description
  6786. GET STATUS                 1    Returns a status code to tell you whether
  6787.                                  the EMM is present and the hardware/software
  6788.                                  is working correctly.
  6789.  
  6790.  GET PAGE FRAME ADDRESS     2    Gives the program the location of the page
  6791.                                  frame.
  6792.  
  6793.  GET UNALLOCATED PAGE       3    Tells the program the totalCOUNT number of
  6794.                                  pages in expanded memory and the number of
  6795.                                  unallocated pages.
  6796.  
  6797.  ALLOCATE PAGES             4    Allocates the number of expanded memory
  6798.                                  pages requested by the program; assigns a
  6799.                                  unique EMM handle to the set of pages
  6800.                                  allocated.
  6801.  
  6802.  MAP HANDLE PAGE            5    Maps the specified logical page in expanded
  6803.                                  memory to the specified physical page within
  6804.                                  the page frame.
  6805.  
  6806.  Function Name              No.  Description
  6807. 
  6808.  DEALLOCATE PAGES           6    Deallocates the pages currently allocated to
  6809.                                  an EMM handle.
  6810.  
  6811.  GET EMM VERSION            7    Returns the version number of the EMM
  6812.                                  software.
  6813.  
  6814.  SAVE PAGE MAP              8    Saves the contents of the page mapping
  6815.                                  registers of all expanded memory boards.
  6816.  
  6817.  RESTORE PAGE MAP           9    Restores the contents of the page mapping
  6818.                                  registers.
  6819.  
  6820.                            10    Reserved.
  6821.  
  6822.                            11    Reserved.
  6823.  
  6824.  GET EMM HANDLE COUNT      12    Returns the number of active EMM handles.
  6825.  
  6826.  GET EMM HANDLE PAGES      13    Returns the number of pages allocated to a
  6827.  Function Name              No.  Description
  6828. GET EMM HANDLE PAGES      13    Returns the number of pages allocated to a
  6829.                                  specific EMM handle.
  6830.  
  6831.  GET ALL EMM HANDLE        14    Returns the active EMM handles PAGES and the
  6832.                                  number of pages allocated to each one.
  6833.  
  6834.  GET/SET PAGE MAP          15    Saves and restores the mapping context of
  6835.                                  the active EMM handle.
  6836.  
  6837.  GET/SET PARTIAL PAGE MAP  16    Saves a partial mapping context for specific
  6838.                                  mappable memory regions in a system.
  6839.  
  6840.  MAP/UNMAP MULTIPLE        17    Maps/unmaps (in a single HANDLE PAGES
  6841.                                  invocation) logical pages into as many
  6842.                                  physical pages as the system supports.
  6843.  
  6844.  REALLOCATE PAGES          18    Increases or decreases the amount of
  6845.                                  expanded memory allocated to a handle.
  6846.  
  6847.  GET/SET HANDLE            19    Lets an application determine and ATTRIBUTE
  6848.  Function Name              No.  Description
  6849. GET/SET HANDLE            19    Lets an application determine and ATTRIBUTE
  6850.                                  set a handle as volatile or non-volatile.
  6851.  
  6852.  GET/SET HANDLE NAME       20    Gets the eight-character name currently
  6853.                                  assigned to a handle assigns an eight-
  6854.                                  character name to a handle.
  6855.  
  6856.  GET HANDLE DIRECTORY      21    Returns information about active handles and
  6857.                                  the names assigned to each.
  6858.  
  6859.  ALTER PAGE MAP AND JUMP   22    Alters the memory mapping context and
  6860.                                  transfers control to the specified address.
  6861.  
  6862.  ALTER PAGE MAP AND CALL   23    Alters the specified mapping context and
  6863.                                  transfers control to the specified address.
  6864.                                  A return can then restore the context and
  6865.                                  return control to the caller.
  6866.  
  6867.  MOVE/EXCHANGE MEMORY      24    Copies or exchanges a region ofREGION memory
  6868.                                  from conventional to conventional memory,
  6869.  Function Name              No.  Description
  6870.                                 from conventional to conventional memory,
  6871.                                  conventional to expanded memory, expanded to
  6872.                                  conventional memory, or expanded to expanded
  6873.                                  memory.
  6874.  
  6875.  GET MAPPABLE PHYSICAL     25    Returns an array with the segment ADDRESS
  6876.                                  ARRAYaddress and physical page number for
  6877.                                  each mappable physicalpage in a system.
  6878.  
  6879.  GET EXPANDED MEMORY       26    Returns an array containing the HARDWARE
  6880.                                  INFORMATION hardware capabilities of the
  6881.                                  expanded memory system.
  6882.  
  6883.  ALLOCATE RAW PAGES        27    Allocates the number of non-standard size
  6884.                                  pages that the operating system requests and
  6885.                                  assigns a unique EMM handle to these pages.
  6886.  
  6887.  ALTERNATE MAP REGISTER    28    Lets an application program SET simulate
  6888.                                  alternate sets of hardware mapping
  6889.                                  registers.
  6890.  Function Name              No.  Description
  6891.                                 registers.
  6892.  
  6893.  PREPARE EXPANDED          29    Prepares the expanded memory MEMORY HARDWARE
  6894.                                  FOR hardware for an impending WARM BOOT warm
  6895.                                  boot.
  6896.  
  6897.  ENABLE/DISABLE OS/E       30    Enables and disables EMM Functions designed
  6898.                                  for use by operating systems and
  6899.                                  environments (Functions 26, 28, and 30).
  6900.  
  6901.  
  6902.  Figure 2:  SAVSCR Main Program
  6903.  
  6904.  #include <c:\msc\include\dos.h>
  6905.  #include <c:\msc\include\memory.h>
  6906.  #include <c:\msc\include\stdio.h>
  6907.  #include <c:\emm\demo\emm.h>
  6908.  
  6909.  /* set up size and base of video ram */
  6910.  #define VIDEO_RAM_SIZE 4000
  6911.  #define VIDEO_RAM_BASE 0XB8000000
  6912.  union REGS    inregs,    outregs;
  6913.  struct SREGS     segregs ;
  6914.  
  6915.  char far *video_ram_ptr = {VIDEO_RAM_BASE); /* video start address (CGA)*/
  6916.  unsigned long int video_ram_size =;         /* bytes in video ram */
  6917.  unsigned int emm_handle ;                   /* our emm handle */
  6918.  char emm_device_name[] = "EMMXXXX0";        /* Device Name of EMM */
  6919.  char emm_handle_name[8] ="shared" ;         /* name for handle to be shared
  6920.  char far *emm_ptr;                          /* pointer to page frame */
  6921.  char far *(*page_ptr) ;                     /* pointer to page in the frame *
  6922.  int pages_needed = 4;
  6923.  struct log_phys {
  6924.    int log_page_number;
  6925.    int phys_page_number;
  6926.         } current_pages [4] ;
  6927.  struct log_phys far *map_unmap_ptr ;
  6928.  int  result ; /* result passed back from function calls */
  6929.  
  6930.  main ()
  6931.  {
  6932.     check_emm_version_number(); /*Check for EMM > 4.0 */
  6933.  
  6934.     result = get_expanded_memory(&emm_ptr, pages_needed, &emm_handle,
  6935.        emm_handle_name);
  6936.     if (result != 0) exit(1);  /* exit if error */
  6937.  
  6938.     result = map_unmap_multiple_pages (current_pages, /* Map in */
  6939.                                        emm_handle, 1); /* pages */
  6940.  
  6941.     move_exchg_to_expanded(MOVE_MEMORY_REGION, /* Copy video screen to */
  6942.     video_ram_ptr, emm_handle, 0, video_ram_size);  /* logical page 0 */
  6943.  
  6944.     /* make a null video screen at logical page 1      */
  6945.     page_ptr = (emm_ptr + 0x4000);        /* make null screen at */
  6946.     memset (page_ptr, 0, VIDEO_RAM_SIZE); /* at logical page 1   */
  6947.  
  6948.   /* Unmap all pages so they are protected  */
  6949.     result = map_unmap_multiple_pages (current_pages, emm_handle, 0);
  6950.  
  6951.  }
  6952.  
  6953.  
  6954.  Figure 3:  This function checks to see if the EMM is present and if the
  6955.             version number is >= 4.0. It uses EMM function 7, GET. VERSION
  6956.             NUMBER.
  6957.  
  6958.  Using EMM Function 7 GET.VERSION NUMBER
  6959.  
  6960.  int check_emm_version_number()
  6961.  {
  6962.     char *emm_device_name_ptr ;
  6963.  
  6964.     inregs.h.ah = 0x35; /* Use the DOS get interrupt function (0x35) to */
  6965.     inregs.h.al =EMM_INT;/* get the pointer at interrupt vector 0x67, */
  6966.     intdosx(&inregs, &outregs, &segregs);/* and check for device name. */
  6967.     emm_device_name_ptr = (segregs.es * 65536) + 10;
  6968.     if (memcmp(emm_device_name, emm_device_name_ptr,8) !=0)
  6969.       {
  6970.         printf("Expanded memory manager not present\n");
  6971.         exit(1);
  6972.       }
  6973.     inregs.h.ah = GET_VERSION ;     /* set function code and check for  */
  6974.     int86(EMM_INT,&inregs,&outregs);/* version >= 4.0                   */
  6975.     if (outregs.h.ah != 0) exit(1);
  6976.     if ((outregs.h.ah == 0) & (outregs.h.al < 0x40))
  6977.     {
  6978.       printf("Expanded memory manager does not support LIM 4.0");
  6979.       exit(1) ;
  6980.     }
  6981.  }
  6982.  
  6983.  
  6984.  Figure 4:  This function gets the amount of expanded memory requested,
  6985.             returns a pointer to the page frame, and assigns the name to the
  6986.             handle.
  6987.  
  6988.  Obtaining the Requested Amount of Memory
  6989.  
  6990.  int get_expanded_memory(emm_ptr_ptr, pages, emm_handle_ptr, name)
  6991.  
  6992.  char *(*emm_ptr_ptr);         /* Pointer to expanded memory page frame */
  6993.  int pages;                    /* Number of pages to allocate */
  6994.  unsigned int *emm_handle_ptr; /* Pointer to emm handle */
  6995.  char *name;
  6996.  
  6997.  {
  6998.     inregs.h.ah = GET_UNALLOCATED_PAGE_COUNT ;  /* Check to see if there */
  6999.     int86(EMM_INT, &inregs, &outregs); /* enough unallocated pages left.*/
  7000.     if (outregs.h.ah != 0) return(1);
  7001.     if (outregs.x.bx < pages) return(2);
  7002.  
  7003.     inregs.h.ah = ALLOCATE_PAGES ;  /* Get a handle and allocate */
  7004.     inregs.x.bx = pages;            /* the requested pages.*/
  7005.     int86(EMM_INT, &inregs, &outregs);
  7006.     if (outregs.h.ah != 0) return(3);
  7007.     *emm_handle_ptr = outregs.x.dx ;
  7008.  
  7009.     inregs.h.ah = GET_FRAME_ADDRESS; /* Get page frame segment address */
  7010.     int86(EMM_INT, &inregs, &outregs); /* and make it a pointer. */
  7011.     if (outregs.h.ah != 0) return(4);
  7012.     *emm_ptr_ptr = (unsigned long int) (outregs.x.bx *65536);
  7013.  
  7014.     inregs.x.ax = SET_HANDLE_NAME ; /* assign name to handle */
  7015.     inregs.x.dx = *emm_handle_ptr ;
  7016.     inregs.x.si = FP_OFF(name) ;
  7017.     segregs.ds  = FP_SEG(name);
  7018.     int86x(EMM_INT, &inregs, &outregs, &segregs);
  7019.     if (outregs.h.ah != 0) return(5);
  7020.  
  7021.     return(0);
  7022.  
  7023.  }
  7024.  
  7025.  
  7026.  Figure 5:  Implementing the MAP/UNMAP MULTIPLE PAGES EMM Function
  7027.  
  7028.  
  7029.  int map_unmap_multiple_pages (log_phys_pages,handle,map_unmap)
  7030.  
  7031.  struct log_phys *log_phys_pages ;  /* Pointer to log_phys struct */
  7032.  unsigned int handle;               /* Handle to map or unmap */
  7033.  unsigned int map_unmap;            /* 0 = map, 1 = unmap */
  7034.  {
  7035.   int i ;
  7036.   struct log_phys *temp_ptr;
  7037.  
  7038.   temp_ptr = log_phys_pages;
  7039.  
  7040.   for (i=0 ; i<=3; i++)
  7041.   {
  7042.   /* Setup the structure to Map or unmap the logical pages 0 to 3 */
  7043.     log_phys_pages->phys_page_number = i;
  7044.     if (map_unmap == 1)
  7045.       log_phys_pages->log_page_number = i;
  7046.     else
  7047.       log_phys_pages->log_page_number = 0xFFFF ;
  7048.  
  7049.     log_phys_pages++ ;
  7050.  
  7051.   }
  7052.  
  7053.   inregs.x.ax = MAP_UNMAP_MULTIPLE_PAGES ;
  7054.   inregs.x.dx = handle;
  7055.   inregs.x.cx = 4;
  7056.   inregs.x.si = FP_OFF(temp_ptr);
  7057.   segregs.ds  = FP_SEG(temp_ptr);
  7058.   int86x(EMM_INT,&inregs,&outregs,&segregs);
  7059.   if (outregs.h.ah != 0) return(1);
  7060.   return(0);
  7061.  }
  7062.  
  7063.  
  7064.  Figure 6:  This function implements the MOVE or EXCHANGE MEMORY  REGION
  7065.             function to move or exchange conventional memory with expanded
  7066.             memory pages.
  7067.  
  7068.  Implementing the MOVE or EXCHANGE MEMORY REGION Function
  7069.  
  7070.  int move_exchg_to_expanded(function_number, conv_buffer,  handle,
  7071.  page, length)
  7072.  unsigned int function_number ; /* Move or Exchange*/
  7073.  char far *conv_buffer ;        /* conventional memory with */
  7074.  int handle;                    /* EMM memory associated with this handle*/
  7075.  int page ;                     /* at this physical page */
  7076.  unsigned long int length;      /* and for this many bytes */
  7077.  
  7078.  {
  7079.  #pragma pack(1) /* Make sure the following structure
  7080.                  is byte aligned */
  7081.  struct move_exchg
  7082.   {
  7083.     unsigned long int region_length;
  7084.     char source_type ;
  7085.     unsigned int source_handle ;
  7086.     unsigned int source_offset ;
  7087.     unsigned int source_seg_page;
  7088.     char dest_type;
  7089.     unsigned int dest_handle;
  7090.     unsigned int dest_offset;
  7091.     unsigned int dest_seg_page;
  7092.    } move_exchg_struct;
  7093.  struct move_exchg *move_exchg_ptr;
  7094.  
  7095.    move_exchg_struct.region_length   = length ;
  7096.    move_exchg_struct.source_type     = 0;
  7097.    move_exchg_struct.source_handle   = 0;
  7098.    move_exchg_struct.source_offset   = FP_OFF(conv_buffer);
  7099.    move_exchg_struct.source_seg_page = FP_SEG(conv_buffer);
  7100.    move_exchg_struct.dest_type       = 1;
  7101.    move_exchg_struct.dest_handle     = handle;
  7102.    move_exchg_struct.dest_offset     = 0 ;
  7103.    move_exchg_struct.dest_seg_page   = page;
  7104.  
  7105.    inregs.x.ax = function_number;
  7106.    move_exchg_ptr = &move_exchg_struct;
  7107.    inregs.x.si = FP_OFF(move_exchg_ptr);
  7108.    segregs.ds  = FP_SEG(move_exchg_ptr);
  7109.    int86x(EMM_INT, &inregs, &outregs, &segregs);
  7110.    if (outregs.h.ah != 0) exit(1);
  7111.  
  7112.    return(outregs.x.ax) ;
  7113.  
  7114.  }
  7115.  
  7116.  
  7117.  Figure 7:  The Main Program for SWPSCR
  7118.  
  7119.  #include <c:\msc\include\dos.h>
  7120.  #include <c:\msc\include\memory.h>
  7121.  #include <c:\msc\include\stdio.h>
  7122.  #include <c:\emm\demo\emm.h>
  7123.  #define VIDEO_RAM_SIZE 4000
  7124.  #define VIDEO_RAM_BASE 0XB8000000
  7125.  
  7126.  union REGS    inregs,    outregs;
  7127.  struct SREGS     segregs ;
  7128.  
  7129.  char far *video_ram_ptr = {VIDEO_RAM_BASE}; /* video start address (CGA)*/
  7130.  unsigned long int video_ram_size ={4000};   /* bytes in video ram */
  7131.  unsigned int emm_handle ;                   /* emm handle */
  7132.  char emm_device_name[] = "EMMXXXX0";        /* Device Name of EMM */
  7133.  char emm_handle_name[8] ="shared" ; /* name for handle to be shared */
  7134.  char far *(*expanded_memory_ptr) ; /* pointer to page frame */
  7135.  char far *(*page_ptr) ;           /* pointer to page in the frame  */
  7136.  long target_time,current_time ;
  7137.  struct log_phys {      /* structure to hold the mapping of logical */
  7138.    int log_page_number; /* pages to physical pages */
  7139.    int phys_page_number;
  7140.         } current_pages [4] ;
  7141.  
  7142.  main ()
  7143.  {
  7144.  
  7145.     check_emm_version_number();
  7146.  
  7147.     search_for_handle(emm_handle_name, &emm_handle);
  7148.  
  7149.     /* Exchange screens*/
  7150.     move_exchg_to_expanded(EXCHANGE_MEMORY_REGION,video_ram_ptr,
  7151.                            emm_handle, 1, video_ram_size);
  7152.  
  7153.     time(¤t_time); /* Delay so user can see screen changes */
  7154.     target_time = current_time + 3;
  7155.     while (current_time < target_time) time(¤t_time);
  7156.  
  7157.    /* Display Screen */
  7158.     move_exchg_to_expanded(EXCHANGE_MEMORY_REGION, video_ram_ptr,
  7159.                            emm_handle, 0, video_ram_size);
  7160.     time(¤t_time); /* Delay so user can see screen change */
  7161.     target_time = current_time + 3;
  7162.     while (current_time < target_time) time(¤t_time);
  7163.  
  7164.     /* Restore original screen */
  7165.     move_exchg_to_expanded(EXCHANGE_MEMORY_REGION,video_ram_ptr,
  7166.                            emm_handle, 1, video_ram_size);
  7167.  
  7168.     result = map_unmap_multiple_pages (current_pages, emm_handle, 0);
  7169.  
  7170.     exit(0) ;
  7171.  }
  7172.  
  7173.  
  7174.  Figure 8:  The Main Program for MAPCALL
  7175.  
  7176.  #include <c:\msc\include\dos.h>
  7177.  #include <c:\msc\include\memory.h>
  7178.  #include <c:\msc\include\stdio.h>
  7179.  #include <c:\emm\demo\emm.h>
  7180.  
  7181.  union REGS    inregs,    outregs;
  7182.  struct SREGS     segregs ;
  7183.  
  7184.  unsigned int emm_handle ;
  7185.  char emm_device_name[] = "EMMXXXX0";                  /* Device Name of EMM *
  7186.  char far emm_handle_name[8] ="mapcall";               /* Name for handle */
  7187.  char far mod1_name[] ="c:\\emm\\demo\\module1.exe\0"; /* Name of modules */
  7188.  char far mod2_name[] ="c:\\emm\\demo\\module2.exe\0"; /* to be loaded */
  7189.  char far *emm_ptr ;
  7190.  char far *(*page_ptr) ;
  7191.  int pages_needed = 16 ;
  7192.  struct log_phys {
  7193.    int log_page_number;
  7194.    int phys_page_number;
  7195.         } ;
  7196.  struct log_phys far current_pages[4];
  7197.  struct log_phys far map_call_pages ;
  7198.  int  result;
  7199.  
  7200.  main ()
  7201.  {
  7202.  
  7203.  
  7204.     check_emm_version_number();
  7205.  
  7206.     result = get_expanded_memory(&emm_ptr, pages_needed, &emm_handle,
  7207.                                  emm_handle_name);
  7208.     if (result != 0) exit(1);
  7209.  
  7210.             /* Map in pages */
  7211.     result = map_unmap_multiple_pages (current_pages,
  7212.                                        emm_handle, 1);
  7213.  
  7214.     *page_ptr = emm_ptr;  /* Load Module 1 into logical page 0  */
  7215.     load_overlay(mod1_name, page_ptr, 0); /* at physical page 0 */
  7216.  
  7217.             /*Load Module 2 into logical page 1 at physical page 1 */
  7218.     load_overlay(mod2_name, page_ptr, 1);
  7219.  
  7220.              /* Unmap all pages */
  7221.     result = map_unmap_multiple_pages (current_pages,
  7222.                                        emm_handle, 0);
  7223.  
  7224.            /* Map and call to module in page 0 and physical page 1 */
  7225.      map_call_pages.log_page_number  = 0;
  7226.  
  7227.      map_call_pages.phys_page_number = 0;
  7228.  
  7229.  
  7230.      map_and_call(&map_call_pages, 1, ¤t_pages, 0, emm_handle);
  7231.  
  7232.     inregs.h.ah = DEALLOCATE_PAGES ; /* Release handle before
  7233.                                         exiting */
  7234.     inregs.x.dx = emm_handle ;
  7235.     int86(EMM_INT, &inregs, &outregs);
  7236.  
  7237.     exit(0);
  7238.  }
  7239.  
  7240.  
  7241.  Figure 9:  Implementing the ALTER PAGE MAP AND CALL EMM Function
  7242.  
  7243.  int map_and_call(new_map_ptr, new_length, old_map_ptr, old_length,
  7244.                   handle)
  7245.  struct log_phys *new_map_ptr;
  7246.  char new_length;
  7247.  struct log_phys *old_map_ptr;
  7248.  char old_length;
  7249.  unsigned int handle;
  7250.  {
  7251.  # pragma pack(1) /* Make sure structure is byte aligned */
  7252.    struct map_call_struct {
  7253.       unsigned int offset_target_address ;
  7254.       unsigned int seg_target_address ;
  7255.       char new_page_map_length ;
  7256.       unsigned int offset_new_page_map;
  7257.       unsigned int seg_new_page_map ;
  7258.       char old_page_map_length ;
  7259.       unsigned int offset_old_page_map;
  7260.       unsigned int seg_old_page_map ;
  7261.     } map_call;
  7262.     struct map_call_struct *map_call_ptr;
  7263.  
  7264.    map_call_ptr = &map_call ;
  7265.    map_call.offset_target_address = 0 ;
  7266.    map_call.seg_target_address    =0xd000;
  7267.    map_call.new_page_map_length   = new_length;
  7268.    map_call.offset_new_page_map   = FP_OFF(new_map_ptr);
  7269.    map_call.seg_new_page_map      = FP_SEG(new_map_ptr);
  7270.    map_call.old_page_map_length   = old_length;
  7271.    map_call.offset_old_page_map   = FP_OFF(old_map_ptr);
  7272.    map_call.seg_old_page_map      = FP_SEG(old_map_ptr);
  7273.  
  7274.    inregs.h.ah =0x56; /* Set up for Alter Page Map and Call EMM function */
  7275.    inregs.h.al = 0;
  7276.    inregs.x.dx = handle ;
  7277.    map_call_ptr = &map_call ;
  7278.    inregs.x.si = FP_OFF(map_call_ptr);
  7279.    segregs.ds = FP_SEG(map_call_ptr);
  7280.    int86x(EMM_INT,&inregs,&outregs,&segregs);
  7281.    if (outregs.h.ah != 0) return(1);
  7282.  
  7283.    return(0) ;
  7284.  }
  7285.  
  7286.  
  7287.  Figure 10:  Implementing the MS-DOS Load Function 4BH This function
  7288.              implements the MS-DOS load function 4BH. It sets AL to 3 causing
  7289.              the function to load the file and apply the relocation factor
  7290.              without executing the file.
  7291.  
  7292.  int load_overlay(load_file_name,relocation_ptr,page)
  7293.  char *load_file_name ;
  7294.  char *(*relocation_ptr);
  7295.  unsigned int page; /* physical page at which to load */
  7296.  
  7297.  {
  7298.  struct reloc {
  7299.    unsigned int load_seg; /* Which segment to load file */
  7300.    unsigned int reloc_factor; /* Which segment to use for
  7301.                                  relocation */
  7302.    } reloc_struct;
  7303.    struct reloc *reloc_struct_ptr;
  7304.  
  7305.    reloc_struct.load_seg = FP_SEG(*relocation_ptr) + (page * 0x400);
  7306.    reloc_struct.reloc_factor = FP_SEG(*relocation_ptr) + (page * 0x400);
  7307.  
  7308.    inregs.h.ah = 0x4B ; /* Dos Exec function code */
  7309.    inregs.h.al = 3;  /* load but do not execute */
  7310.    inregs.x.dx  = FP_OFF(load_file_name);
  7311.    segregs.ds   = FP_SEG(load_file_name);
  7312.  
  7313.    reloc_struct_ptr = &reloc_struct ;
  7314.    inregs.x.bx  = FP_OFF(reloc_struct_ptr);
  7315.    segregs.es  = FP_SEG(reloc_struct_ptr);
  7316.  
  7317.    intdosx(&inregs, &outregs, &segregs);
  7318.  
  7319.  }
  7320.  
  7321.  
  7322.  Figure 11:  NAME Start
  7323.  
  7324.  ; Use the DOSSEG directive to ensure that the code segment is the
  7325.  ; first segment in the module. Since this piece of code will be
  7326.  ; loaded in at the page frame at D000:0000H the Alter Page Map and
  7327.  ; Call will use D000:0000H as the entry point.
  7328.  
  7329.  DOSSEG
  7330.  
  7331.  data SEGMENT PUBLIC 'DATA'
  7332.  Data  ends
  7333.  
  7334.  CODE SEGMENT PUBLIC 'CODE'
  7335.       ASSUME CS:CODE, DS:DATA
  7336.  start proc far
  7337.       push    ds
  7338.       PUSH    dx
  7339.       mov     DX,data ; set up data seg into ds
  7340.       mov     ds,dx
  7341.       pop     dx
  7342.       mov     ah, 09  ; set function code for DOS display string
  7343.       mov     dx, offset enter_msg
  7344.       int    21H
  7345.       mov    si, offset handle_name
  7346.       mov    ax, 5401H              ; set function to search for named
  7347.                                     ; handle
  7348.       int    67H                    ; and invoke EMM
  7349.       or ah, ah
  7350.       jnz exit
  7351.  
  7352.       mov     si, offset map_call ; Set up registers for Alter Page
  7353.                                   ; Map and Call
  7354.       mov     al, 0               ; indicate that values are pages
  7355.       mov     ah, 56H             ; set function to map and call
  7356.       int     67H                 ; invoke emm
  7357.       or ah, ah
  7358.       jnz exit
  7359.  
  7360.       mov     ah, 09                ; set function for DOS display
  7361.                                     ; string
  7362.       mov     dx, offset exit_msg
  7363.       int 21H
  7364.  exit:
  7365.       pop     ds
  7366.       ret
  7367.  start endp
  7368.  CODE ENDS
  7369.  data SEGMENT PUBLIC 'DATA'
  7370.  ;
  7371.  cr    equ  0Dh
  7372.  lf    equ  0AH
  7373.  ;
  7374.  log_phys_map_struct   STRUC
  7375.     log_page_number    DW ?
  7376.     phys_page_number   DW ?
  7377.  log_phys_map_struct   ENDS
  7378.  
  7379.  map_call_struct      STRUC
  7380.      target_address      DD ?   ;  Pointer to which EMM will transfer
  7381.                                 ;  control
  7382.      new_page_map_length DB ?   ;  Number of new pages to be mapped on
  7383.                                 ;  call.
  7384.      new_page_map_ptr    DD ?   ;  Pointer to array of
  7385.                                 ;  log_phys_map_struc.
  7386.      old_page_map_length DB ?   ;  Number of pages to mapped on
  7387.                                 ;  return.
  7388.      old_page_map_ptr    DD ?   ;  pointer to array of
  7389.                                 ;  log_phys_map_struc
  7390.      reserved            DW 4 DUP (?)
  7391.  map_call_struct ENDS
  7392.  ;
  7393.  new_map  log_phys_map_struct <1,1>   ;  mapping before call
  7394.  old_map  log_phys_map_struct <0,0>   ;  mapping after call
  7395.  
  7396.  
  7397.  map_call  map_call_struct <0D0004000H,1,new_map,1,old_map>
  7398.  
  7399.  handle_name db 'mapcall',0           ; handle name is ascciz string
  7400.  enter_msg   db 'Entering Module 1',cr,lf,'$'
  7401.  exit_msg    db 'Exiting Module 1',cr,lf,'$'
  7402.  Data  ends
  7403.  end  start
  7404.  
  7405.  
  7406.  Figure 12:  NAME Start  (MODULE2.EXE)
  7407.  
  7408.  DOSSEG
  7409.  
  7410.  data SEGMENT PUBLIC 'DATA'
  7411.  Data  ends
  7412.  
  7413.  CODE SEGMENT PUBLIC 'CODE'
  7414.       ASSUME CS:CODE, DS:DATA
  7415.  start proc far
  7416.       push    ds
  7417.       PUSH    dx
  7418.       mov     DX,data ; Set up date segment
  7419.       mov     ds,dx
  7420.       pop     dx
  7421.       mov     ah, 09 ; Set function code for DOS display string.
  7422.       mov     dx, offset enter_msg
  7423.       int 21H
  7424.       mov     ah, 09 ; Set function code for DOS display string.
  7425.       mov     dx, offset exit_msg
  7426.       int 21H
  7427.       pop     ds
  7428.       ret
  7429.  start endp
  7430.  CODE ENDS
  7431.  
  7432.  data SEGMENT PUBLIC 'DATA'
  7433.  cr    equ  0Dh
  7434.  lf    equ  0AH
  7435.  enter_msg   db 'Entering Module 2',cr,lf,'$'
  7436.  exit_msg    db 'Exiting Module 2',cr,lf,'$'
  7437.  Data  ends
  7438.  end  start
  7439.  
  7440.  
  7441.  ════════════════════════════════════════════════════════════════════════════
  7442.  
  7443.  
  7444.  Vol. 3 No. 2  Table of Contents
  7445.  
  7446.  Microsoft(R) Windows Adapts to the Unique Needs of the Japanese Market
  7447.  
  7448.  A Japanese version of Microsoft Windows offers support for double-byte kanji
  7449.  characters and has the ability to convert kana characters into kanji. It
  7450.  also enables software vendors to build graphical applications that run on
  7451.  many disparate Japanese hardware systems despite their incompatibility.
  7452.  
  7453.  
  7454.  Utilizing OS/2 Multithread Techniques in Presentation Manager Applications
  7455.  
  7456.  While OS/2 provides the preemptive multitasking that Windows lacks, the
  7457.  Presentation Manager's message-based architecture can cause problems when
  7458.  doing lengthy processing. OS/2 threads offer alternative solutions to the
  7459.  problems that occur when programming in Presentation Manager.
  7460.  
  7461.  
  7462.  OS/2 LAN Manager Provides a Platform for Server-Based Network Applications
  7463.  
  7464.  The OS/2 LAN Manager API, with its named pipe and remote procedure call
  7465.  facilities, offers a rich set of functions for building network intrinsic
  7466.  applications. These applications can exploit the potential of distributed
  7467.  processing from an OS/2 or DOS workstation.
  7468.  
  7469.  
  7470.  Writing OS/2 Bimodal Device Drivers: An Examination of the DevHlp API
  7471.  
  7472.  Device drivers are the necessary linkage between the operating system and
  7473.  peripheral devices that enable you to use special hardware with standard
  7474.  applications software. Our study of OS/2 continues with an in-depth look at
  7475.  the unique capabilities of OS/2 device drivers.
  7476.  
  7477.  
  7478.  Exploring the Structure and Contents of the MS-DOS(R) Object Module
  7479.  Format
  7480.  
  7481.  A programmer does not usually need to think about the contents of the
  7482.  intermediate files prepared for the linker when using commercial compilers.
  7483.  This excerpt from The MS-DOS(R) Encyclopedia examines the object module
  7484.  format and provides insights into the operation of LINK and LIB.
  7485.  
  7486.  
  7487.  A Guide to Program Editors, the Developer's Most Important Tool
  7488.  
  7489.  Text editors for programmers have improved greatly, prompting MSJ to conduct
  7490.  an evaluation of twelve editors. The three editors that we thought were
  7491.  best──BRIEF(R), ME, and VEDIT PLUS──are reviewed in depth, and a features
  7492.  chart gives you a good idea of what all twelve can do.
  7493.  
  7494.  
  7495.  EDITOR'S NOTE
  7496.  
  7497.  This issue of MSJ covers the broadest range of topics yet. In addition to
  7498.  our continuing coverage of the OS/2 systems with articles on the OS/2 LAN
  7499.  Manager, bimodal device drivers, and the Presentation Manager, we have an
  7500.  article on developing software for the Japanese market, a tutorial on the
  7501.  MS-DOS(R) object file format, and a comprehensive look at available
  7502.  program editors.
  7503.  
  7504.  As time goes by, we pass yet another milestone in the progress of OS/2.
  7505.  With the shipment of Version 1.0 of the standard edition, OS/2 has become
  7506.  a users' as well as a developers' product, and many developers have begun
  7507.  to focus on the advanced managers. The Presentation Manager is no longer
  7508.  just a concept or a specification; now that it is in developers' hands, it
  7509.  is a real development environment. In this issue, Ray Duncan explains how
  7510.  to write a device driver for OS/2, and Charles Petzold examines the use of
  7511.  OS/2 threads within Presentation Manager
  7512.  programs.
  7513.  
  7514.  OS/2 has the potential not only to improve the applications we associate
  7515.  with MS-DOS, but to open the way for entirely new classes of applications.
  7516.  Among the most exciting of these is distributed processing. PC networking
  7517.  to date has in most cases meant little more than file and printer sharing.
  7518.  We present an overview of the possibilities for the development of
  7519.  server-based distributed processing applications with the OS/2 LAN Manager.
  7520.  
  7521.  Recently in talking with subscribers, we were reminded just how important,
  7522.  and how personal, the choice of a program editor is to the professional
  7523.  programmer. We heard many different opinions, all of which were
  7524.  uncharacteristically strong. Given the importance of the subject, we
  7525.  thought it worthwhile to depart from our normal subject material and
  7526.  commissioned a comprehensive review of the available editors. The results
  7527.  were surprising. Please drop us a line if you think we should review
  7528.  other categories of programmer tools.
  7529.  
  7530.  We want this to be your Journal. Please write and tell us how we can make
  7531.  it better for you.──Ed.
  7532.  
  7533.  Masthead
  7534.  
  7535.  JONATHAN D. LAZARUS
  7536.  Editor and Publisher
  7537.  
  7538.  EDITORIAL
  7539.  
  7540.  TONY RIZZO
  7541.  Technical Editor
  7542.  
  7543.  CHRISTINA G.DYAR
  7544.  Associate Editor
  7545.  
  7546.  JOANNE STEINHART
  7547.  Production Editor
  7548.  
  7549.  GERALD CARNEY
  7550.  Staff Editor
  7551.  
  7552.  KIM HOROWITZ
  7553.  Editorial Assistant
  7554.  
  7555.  ART
  7556.  
  7557.  MICHAEL LONGACRE
  7558.  Art Director
  7559.  
  7560.  VALERIE MYERS
  7561.  Associate Art Director
  7562.  
  7563.  CIRCULATION
  7564.  
  7565.  WILLIAM B. GRANBERG
  7566.  Circulation Manager
  7567.  
  7568.  L. PERRIN TOMICH
  7569.  Assistant to the Publisher
  7570.  
  7571.  DONNA PUIZINA
  7572.  Administrative Assistant
  7573.  
  7574.  Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
  7575.  in part or in whole without permission is prohibited.
  7576.  
  7577.  Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
  7578.  NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
  7579.  Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
  7580.  President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
  7581.  William Neukom, Secretary.
  7582.  
  7583.  Microsoft Corporation assumes no liability for any damages resulting from
  7584.  the use of the information contained herein.
  7585.  
  7586.  Microsoft, XENIX, CodeView, and MS-DOS are registered trademarks of
  7587.  Microsoft Corporation. IBM and PC/AT are registered trademarks of
  7588.  International Business Machines Corporation. Personal System/2 and PS/2 are
  7589.  trademarks of International Business Machines Corporation. BRIEF is a
  7590.  registered trademark of UnderWare, Inc. CP/M is a registered trademark of
  7591.  Digital Research, Inc. dBASE and dBASE III PLUS are registered trademarks of
  7592.  Ashton-Tate Corporation. Hercules is a registered trademark of Hercules
  7593.  Computer Technology. Intel is a registered trademark of Intel Corporation.
  7594.  Macintosh is a registered trademark of Apple Computer, Inc. NEC is a
  7595.  registered trademark of NEC Corporation. Paradox is a registered trademark
  7596.  of Ansa Software, a Borland Company. 3+Open is a trademark of 3Com
  7597.  Corporation. Solution Systems is a registered trademark of Solution Systems.
  7598.  UNIX is a registered trademark of American Telephone and Telegraph Company.
  7599.  WordStar is a registered trademark of MicroPro International Corporation.
  7600.  WYSE is a trademark of WYSE Technology.
  7601.  
  7602.  ████████████████████████████████████████████████████████████████████████████
  7603.  
  7604.  Bringing Windows to the Expanding Japanese Market
  7605.  
  7606.  by Tom Sato and Lin F. Shaw
  7607.  
  7608.  Machine independence is a cornerstone of the Microsoft(R) Windows
  7609.  philosophy. While this particular issue is largely ignored in the United
  7610.  States-due to the fact that the PC environment is dominated by IBM(R) PC
  7611.  AT(R) machines and compatibles──in Japan, Windows-based software is truly
  7612.  compatible across incompatible hardware architectures.
  7613.  
  7614.  Microsoft Windows has given the Japanese community of independent
  7615.  software vendors (ISVs) the opportunity to develop applications with
  7616.  only one set of source codes for a wide range of machines, thus
  7617.  eliminating the laborious adaptation of software to different machines.
  7618.  For Western ISVs there is an added advantage. Windows applications
  7619.  written for IBM PCs and compatibles run on all the various Japanese
  7620.  computers. Windows-based applications, therefore, present a tremendous
  7621.  export opportunity for Western ISVs, since Japan is a country where real
  7622.  growth in the software industry has yet to come (see Figures 1 and 2).
  7623.  
  7624.  Microsoft Windows Version 1.03 was first released by NEC in December 1986
  7625.  for use on its PC-9801 series of personal computers, and in its first six
  7626.  months sold over 25,000 copies. Matsushita has also joined the original
  7627.  family of Windows equipment manufacturers (OEMs) by releasing their own
  7628.  adaptations of Windows for their new machines at the September 1987 Tokyo
  7629.  DATA show (see Figures 3 and 4).
  7630.  
  7631.  
  7632.  The Japanese Market
  7633.  
  7634.  In 1982, NEC launched the PC-9801 series. Fourteen compatible variations
  7635.  later, with its millionth unit shipped last March, the PC-9801 series
  7636.  dominates the Japanese PC market, with a market share estimated at over
  7637.  60 percent. The rest of the market is split among various companies, all
  7638.  of which have released incompatible MS-DOS(R) machines.
  7639.  
  7640.  In spite of these numbers, however, the fact is that the penetration rate
  7641.  of personal computers in the Japanese office is nowhere near that of the
  7642.  United States, as many Japanese businessmen have yet to become accustomed
  7643.  to PCs. The primary reason is that the Japanese language has a great
  7644.  many character sets and kanjis (Chinese characters). This has prevented
  7645.  the spread of mechanical typewriters──they have more than 4000 characters
  7646.  and are very clumsy to use──and, therefore, computers. Amazingly, most
  7647.  business correspondence in Japan is still done by hand.
  7648.  
  7649.  Fortunately, there is a sign that this is all going to change. Several
  7650.  years ago, some of the electronics giants started to sell electronic
  7651.  Japanese typewriters. For the most part, these are 8-bit computers with
  7652.  an LCD screen, a thermal transfer printer and built-in syllabic kana to
  7653.  kanji conversion software. These machines provide more capability,
  7654.  portability, efficiency, and also better prices than the previous
  7655.  generation of mechanical typewriters. This is the latest booming
  7656.  business in Japan, with an installed base of more than 5 million
  7657.  units.
  7658.  
  7659.  Nevertheless, these electronic typewriters still have limitations──they
  7660.  are not as flexible as word-processing software on 16-bit PCs; storage
  7661.  is limited and the editing screen is really too small for general office
  7662.  use. However, the popularity of the Japanese electronic typewriters
  7663.  has led to significant increases in the sale of substantially more
  7664.  powerful 16-bit PCs.
  7665.  
  7666.  The standard configuration for a typical Japanese PC is an 80286 running
  7667.  at 10 MHz, a 640x480 pixel screen, 640Kb of RAM, 256Kb of ROM for kanji
  7668.  fonts, and dual 1.2Mb floppy disk drives. Machines with an internal 20Mb
  7669.  hard disk drive are also becoming more popular, and there are now
  7670.  machines with higher screen resolution and more detailed kanji fonts
  7671.  available. However, the speed of Japanese computers is slower than that of
  7672.  their counterparts in the United States. This is largely due to a great
  7673.  deal of overhead, for instance searching for kanji bitmaps and having to
  7674.  deal with larger screen and kana-kanji interfaces.
  7675.  
  7676.  
  7677.  Japanese Language
  7678.  
  7679.  Modern Japanese is written in a mixture of kanji characters and two types
  7680.  of kana signs. There are 46 basic kana signs; 45 basic syllables arranged
  7681.  as in Figure 5, consisting of five vowels in the order a, i, u, e, o and
  7682.  then five vowels preceeded in turn by nine consonants in the order k, s
  7683.  (or alternatively sh), t (or ts), n, h (f), m, y, r, w, and the final
  7684.  consonant for kana, n.
  7685.  
  7686.  Two small strokes on the upper right hand corner of a character are
  7687.  added as voiced marks or dakuten. They apply to kana beginning with k, s,
  7688.  t, and h and represent 20 more syllables beginning with voiced
  7689.  consonants g, z, d, and b respectively. A small round circle on the
  7690.  upper right hand corner is a semi voicing sound mark (han-dakuten). It
  7691.  applies to the kana beginning with h and represents five further
  7692.  syllables begining with voiced consonants p. Further, nine of the kanas
  7693.  are sometimes written in small characters to form dipthongs (see
  7694.  Figure 6).
  7695.  
  7696.  There are two types of kana signs: kata-kana and hira-gana There is a
  7697.  one-to-one correspondence between the kata-kana and hira-gana; for
  7698.  example, for each syllable there is both a hira-gana letter and kata-kana
  7699.  letter. Hira-gana is most commonly used with a mixture of kanji, and kata-
  7700.  kana is always used for foreign words. Kata-kana is a simplified form of
  7701.  hira-gana, written for the most part in simple strokes, whereas hira-ganas
  7702.  are written with curves. On an 8x8 bitmap, hira-gana is too intricate to
  7703.  describe. Therefore, both the Japanese keyboard and the Japanese 1-byte
  7704.  character code for alphanumeric characters contain only the kata-kana
  7705.  (see Figure 7).
  7706.  
  7707.  
  7708.  Kanji Characters
  7709.  
  7710.  Kanji characters are used frequently to represent one or more kana
  7711.  characters. There are many homonyms in Japanese and the use of kanji
  7712.  clarifies the meaning of the written word. Nouns and verbs are typically
  7713.  kanjis or a mixture of hira-ganas and kanjis. A typical kanji dictionary
  7714.  lists over 15,000 kanji characters. However, the Japanese elementary
  7715.  schools only teach 1878 of them and in practice about 3500 kanjis have
  7716.  normal daily use.
  7717.  
  7718.  The same kanji character may have different pronunciations depending on
  7719.  its context or the other kanji with which it forms a word. For example, a
  7720.  kanji character for the word "leaf" has three pronunciations: ha, ba, and
  7721.  yo-u. Conversely, the same pronunciation is used for many kanji; for
  7722.  example, as many as twenty different kanjis can be pronounced ai.
  7723.  
  7724.  
  7725.  Text Layout
  7726.  
  7727.  Japanese is traditionally written vertically in columns running from top
  7728.  to bottom and from right to left across the page. Most books and
  7729.  newspapers are written this way. However, it can also be written
  7730.  horizontally as English is. Business correspondence, in fact, is usually
  7731.  written horizontally. Most Japanese software, therefore, displays
  7732.  horizontally and only supports vertical columns when printing a document
  7733.  or on a screen in sophisticated desktop publishing software.
  7734.  
  7735.  Japanese text is written without spaces between words. The use of a
  7736.  mixture of kanji and kana in a regular sentence provides implicit breaks
  7737.  to a sentence and the Japanese would have a very difficult time reading
  7738.  text of only kana characters. It would be analogous to reading English
  7739.  text without spaces between words.
  7740.  
  7741.  Japanese text can wrap from one line to another at any point, since there
  7742.  are no spaces between words. This includes two successive kanji which
  7743.  make up one logical "word." There is no need to justify Japanese text, as
  7744.  all Japanese characters are the same size.
  7745.  
  7746.  
  7747.  Shift-JIS Code
  7748.  
  7749.  Japanese Industrial Standards (JIS) define two levels of kanjis for
  7750.  computers. The JIS C 6226 standard defines the image and the code of the
  7751.  3500 most commonly used kanji characters as level 1 kanjis. Level 2
  7752.  defines the optional additional 2500 kanjis which are more obscure and are
  7753.  used for the names of people and places. Most 16-bit computers in Japan
  7754.  now support level 2, making the number of kanjis usually available to
  7755.  Windows more than 6000. They are stored in bank-switched ROMs as 16x16
  7756.  pixel images.
  7757.  
  7758.  Kanjis, as well as hira-ganas, kata-kanas, alphanumeric characters, Greek
  7759.  and Russian characters, and other signs supported in ASCII character
  7760.  sets, are coded in the JIS coding scheme. JIS was designed like ASCII to
  7761.  be a transmission standard as well as a storage standard. When
  7762.  transmitting data over communications lines, reliability is extremely
  7763.  important. That is why parity exists for 7-bit ASCII.
  7764.  
  7765.  The JIS standard was also encoded in 7 bits to use parity. In order to be
  7766.  able to distinguish between 7-bit ASCII and 7-bit JIS, the ASCII shift-in
  7767.  (SI) and shift-out (SO) controls were used. A kanji character is composed
  7768.  of two successive 7-bit patterns, limiting us to 16,384 values. In order
  7769.  to avoid the 32 control characters, space, and rub-out, there are only
  7770.  94x94 (8836) available and defined characters.
  7771.  
  7772.  As the use of parity in personal computers became less important, and
  7773.  since many U.S. applications did not understand SI and SO, Microsoft
  7774.  defined a new kanji encoding scheme for MS-DOS; the Microsoft Kanji
  7775.  Encoding Scheme, which uses two 8-bit values. It is commonly known as the
  7776.  Shift-JIS Code, because the character codes of JIS have been shifted.
  7777.  
  7778.  Figures 7 and 8 describe how this works. Of all the codes available
  7779.  between 00H and FFH, 00H to 7FH are ASCII characters and control codes,
  7780.  and A0H to DFH are kata-kana characters. This leaves 64 positions for
  7781.  the first byte of the double-byte value. The second byte could be between
  7782.  00H and FFH. Since 64x256 is 16,384, there is more than enough room for
  7783.  all the JIS characters.
  7784.  
  7785.  Since JIS has a 94x94 matrix, Microsoft decided to map it into a 47x188
  7786.  matrix in the set of 64x256 possibilities. This allowed 68 values to be
  7787.  avoided for the second byte. The control characters between 00H-1FH and
  7788.  7FH-FFH were not used in order to avoid conflict with U.S. applications,
  7789.  nor were FDH and FEH used, because CP/M(R) 86 was using them when the
  7790.  scheme was introduced. Also, 20H-3FH were avoided in order to make
  7791.  parsing of characters such as ";", "(,)", and ":" easier for U.S.
  7792.  language compilers.
  7793.  
  7794.  Figure 8 shows how the Microsoft Kanji Encoding Scheme is shifted from
  7795.  the JIS coding scheme without actually changing the original JIS
  7796.  ordering. Just remember that a code between 81H-9FH and E0H-FCH means
  7797.  that the following code is the second byte of the double-byte character
  7798.  and together they give the code of a kanji character.
  7799.  
  7800.  
  7801.  Kana-Kanji Conversion
  7802.  
  7803.  With all these hira-gana, kata-kana and kanji characters, making a
  7804.  computer to support the Japanese language becomes complex. On the MS-DOS
  7805.  level, to input Japanese, a kana-kanji conversion program is installed
  7806.  as a device driver. There are various kana-kanji conversion programs on
  7807.  the market and most of them are porting their software to run under
  7808.  Windows. Usually kana-kanji conversion units are packaged free of charge
  7809.  with applications software. Companies lacking a kana-kanji conversion
  7810.  program can buy a license from kana-kanji software vendors.
  7811.  
  7812.  These conversion programs are typically activated by pressing a "hot
  7813.  key." In the case of the NEC(R) PC-9801 series, for example, the hot-key
  7814.  combination is CTRL-XFER and the bottom of the screen becomes the
  7815.  conversion window.
  7816.  
  7817.  There are various methods of input but all have kata-kana input and
  7818.  romanji (Roman character) input. Although the keyboard supports all the
  7819.  kata-kana characters, many Japanese are familiar with the standard
  7820.  English QWERTY typewriter keyboard and prefer to input using Roman
  7821.  characters. For example, the word "kanji" is typed K A N J I and as you
  7822.  type it in, the conversion program accepts the Roman characters and
  7823.  displays the correct hira-ganas. When the <XFER> key is pressed, the
  7824.  program searches through its dictionary to find the kanji characters for
  7825.  that word.
  7826.  
  7827.  This process is complicated for various reasons, including the Japanese
  7828.  homonyms mentioned earlier. Secondly, the inflectional endings of
  7829.  Japanese verbs increase the number of word roots so much that the
  7830.  dictionary may have to contain well over 100,000 word roots. Obviously,
  7831.  this would require an enormous amount of memory.
  7832.  
  7833.  The problem is solved by software that draws on a grammar of Japanese
  7834.  inflection to analyze the phonetic spelling typed in by the user. The
  7835.  software displays the most appropriate and most commonly used kanji
  7836.  combinations. Good software will give the correct kanji approximately 80
  7837.  percent of the time. If the software gives the wrong kanjis, the user can
  7838.  hit the <XFER> key again and the next kanji choice will appear. To see
  7839.  all the different ways of spelling the word, you press <SHIFT>-<XFER>.
  7840.  Some conversion packages can convert multiple phrases, even a whole
  7841.  sentence. Figure 9 shows the kana-kanji conversion unit as it would
  7842.  appear in a Windows session.
  7843.  
  7844.  
  7845.  Kanji Classification
  7846.  
  7847.  Microsoft Windows 1.03 applications can be classified according to their
  7848.  abilities to process kanji input. Please note that this specification is
  7849.  only intended for Windows 1.03. There will be additions in the Windows
  7850.  2.0 specification.
  7851.  
  7852.  Level 1: Original U.S. Applications. These applications are not able to
  7853.  treat double-byte kanji text. Most of the first releases of U.S.
  7854.  applications are level 1 applications.
  7855.  
  7856.  Level 2: Modified U.S. Applications. These applications can process
  7857.  double-byte kanji characters as a single entity. They accept and display
  7858.  shift-JIS kanji inputs, advance the cursor and handle backspaces with
  7859.  kanji text, sort kanji text, and do right justification on kanji text.
  7860.  However, these applications do not handle in-line kana-kanji conversion,
  7861.  and the conversion process is transparent to these applications. The
  7862.  conversion is done by a separate conversion module called KKAPP which is
  7863.  activated and deactivated using special keys. In-line conversion means
  7864.  that the kana and kanji input actually takes place in the applications
  7865.  window and not in the pop-up windows at the bottom of the screen. KKAPP
  7866.  defines the user interface for kanji conversion for all level 2
  7867.  applications. Most kanji applications are currently at this level.
  7868.  
  7869.  Level 3: Applications with Full Kanji Support. These applications can
  7870.  handle kana-kanji conversion. They call the KKAPP using the level 3
  7871.  application function ConvertRequest. They allow the user to change a
  7872.  kanji selection from a list of kanji candidates having the same
  7873.  pronunciation, input kanji strings without using an explicit convert
  7874.  command (free input), do full sentence or full paragraph conversion, and
  7875.  select kanji candidates from a menu list using the mouse.
  7876.  
  7877.  
  7878.  Conversion Under Windows 1.03
  7879.  
  7880.  Under MS-DOS, the conversion module traps the keyboard input, converts it
  7881.  to the appropriate 2-byte characters and then transfers those
  7882.  characters to MS-DOS. Under Windows there are six modules that are
  7883.  involved in kana-kanji conversion: the keyboard driver, USER, FEP,
  7884.  KKAPP, KKLIB, and the application. Figure 10 describes each of the
  7885.  steps.
  7886.  
  7887.  
  7888.  Keyboard Driver
  7889.  
  7890.  Kana-kanji conversion starts with the user typing in a key. This keyboard
  7891.  input is processed by the Windows keyboard driver and sent to USER.
  7892.  
  7893.  The USER library module turns keyboard input into a Windows message
  7894.  (either WM_KEYUP or WM_KEYDOWN) and sends the message to the appropriate
  7895.  module. If WH_KEYBOARD has been set using SetWindowsHook, then USER sends
  7896.  the keyboard messages to the keyboard filter; otherwise, USER sends the
  7897.  keyboard message to the current application.
  7898.  
  7899.  The KKLIB library module sits between USER and KKAPP and controls the
  7900.  flow of keyboard input. Upon request from KKAPP, KKLIB will install the
  7901.  keyboard filter function to receive all the keyboard input. In the filter
  7902.  function, KKLIB decides whether to send the keyboard message to KKAPP or
  7903.  to return the keyboard message to USER without processing it (as when the
  7904.  kana-kanji conversion mode is turned off).
  7905.  
  7906.  KKLIB also provides an entry point in level 3 applications for kana-kanji
  7907.  conversion requests or inquiries. This function in KKLIB
  7908.  (ConvertRequest()) in turn translates these requests into Windows
  7909.  messages (WM_ CONVERTREQUEST) and sends the messages to KKAPP.
  7910.  
  7911.  The KKAPP application module does the kana-kanji conversion. Depending
  7912.  on which kind of application is using KKAPP (level 2 or level 3), KKAPP
  7913.  will take the appropriate actions. If a level 2 application is the
  7914.  active window and the conversion mode is on, KKAPP will open up its
  7915.  conversion window to display the conversion process, and send the
  7916.  final result to the application through WM_CHAR messages. If a level 3
  7917.  application is active, KKAPP does not display the conversion process
  7918.  and simply answers convert requests from the application. One KKAPP is
  7919.  required for each FEP.
  7920.  
  7921.  The front end processor module does the actual conversion. This module
  7922.  has the dictionary and the conversion algorithm and KKAPP interfaces
  7923.  directly to it. It is installed as an MS-DOS device driver.
  7924.  
  7925.  A level 2 application is not involved in kana-kanji conversion. It simply
  7926.  receives the converted results in WM_CHAR messages from KKAPP. A level 3
  7927.  application does participate in kana-kanji conversion by calling
  7928.  ConvertRequest() and receives the results back in the KanjiStruct
  7929.  parameter.
  7930.  
  7931.  
  7932.  Double-Byte Kanji
  7933.  
  7934.  Writing a Windows application for the Japanese market involves a number
  7935.  of things. First, all messages and resources in the resource file need to
  7936.  be translated into Japanese. The double-byte support code of the
  7937.  resource compiler is enabled when the keyword KANJI is seen in the
  7938.  resource file. This keyword must be followed by four numbers stating the
  7939.  two ranges of valid first byte ranges of kanji. Figure 11 provides an
  7940.  example of the translated resource file for the clipboard desktop
  7941.  application.
  7942.  
  7943.  Windows virtualizes 2-byte support by providing string handling routines.
  7944.  Applications should not make any assumption about the system they are
  7945.  running on. Using the function AnsiUpper to convert to an upper character
  7946.  or the function AnsiLower to convert to a lower character enables the
  7947.  same application to run correctly both in the U.S. and in other
  7948.  countries. Windows further provides the AnsiNext call to scan forward and
  7949.  AnsiPre to scan backwards. It is important to use these calls so that an
  7950.  application does not get out of sync and misinterpret consecutive
  7951.  kanjis as different kanjis.
  7952.  
  7953.  Another problem is that the second byte of the shift-JIS character set
  7954.  overlaps with Roman characters starting at 40H. Applications should be
  7955.  careful not to mistreat the second byte of a kanji as a Roman character,
  7956.  particularly when implementing the searching function or when parsing
  7957.  path names looking for the '\' file separator.
  7958.  
  7959.  In screen editing, applications that use Windows Edit Control will run
  7960.  without modification in Japan. Applications that do not use Edit
  7961.  Control should buffer two WM_CHAR messages to make one single TextOut()
  7962.  call when 2-byte kanjis are encountered. When the application is
  7963.  advancing, backspacing, positioning the cursor, and line-wrapping on
  7964.  the screen, it must treat 2-byte kanjis as one entity.
  7965.  
  7966.  Lastly, sorting that involves kanji is slightly more complicated. The
  7967.  shift-JIS character set places its 1-byte kanas in the A0H-CFH range,
  7968.  and the kanjis in the two ranges 80H-9FH and E0H-FCH. In Japan, 1-byte
  7969.  kanas are sorted after 1-byte Ro-man characters in the 20H-7EH range, but
  7970.  before all the 2-byte characters. This necessitates swapping kana
  7971.  characters with the kanjis which are in the 80H-9FH range when doing a
  7972.  sort.
  7973.  
  7974.  
  7975.  In Conclusion
  7976.  
  7977.  Microsoft is currently working on an additional specification for the
  7978.  kana-kanji interface for Windows 2.0. In the meantime, ISVs should be
  7979.  able to see that a major opportunity exists to export Windows-based
  7980.  applications to the Japanese market. Hopefully, the information
  7981.  provided here will offer some insight into the work required to pursue
  7982.  it. ISVs interested in developing Japanese Windows applications are
  7983.  urged to contact the OEM Technical Support Group, Microsoft KK, Sanban cho
  7984.  Yayoi kan, 6-2 Sanban cho, Chiyoda ku, Tokyo, JAPAN, for more detailed
  7985.  information. We look forward to hearing from you.
  7986.  
  7987.  
  7988.  Figure 8:  Microsoft Kanji Encoding Scheme
  7989.  
  7990.  00 
  7991.  
  7992.  10          21                    7E
  7993.                                    
  7994.  20      21┌───────────────────────┐
  7995.              │                       │
  7996.  30         │                       │
  7997.              │      JIS level 1      │
  7998.  40         │                       │
  7999.              │                       │
  8000.  50         ├───────────────────────┤
  8001.              │                       │
  8002.  60         │      JIS level 2      │
  8003.              │                       │
  8004.  70         │                       │
  8005.           7E└───────────────────────┘
  8006.  80               81┌──────────────┐┌───────┬──────────────────────┐
  8007.                       │     Microsoft translation of JIS level 1     │
  8008.  90                  │              ││       │                      │
  8009.                       ├──────────────┤├───────┼──────────────────────┤
  8010.  A0               9F└─────────────┘└───────┴─────────────────────┘
  8011.                         Microsoft translation of JIS level 2, part 1
  8012.  B0                                                            
  8013.                       │              │ │     │ │                     │
  8014.  C0                 40             7E 80   9E 9F                   FC
  8015.                       │              │ │     │ │                     │
  8016.  D0                  │              │ │     │ │                     │
  8017.                                                                 
  8018.  E0               E0┌──────────────┐┌───────┬──────────────────────┐
  8019.                    EF│ Microsoft translation of JIS level 2, part 2 │
  8020.  F0               F0├──────────────┤├───────┼──────────────────────┤
  8021.                    FCExtra room for extensions to the JIS kanji standard
  8022.                       └──────────────┘└───────┴──────────────────────┘
  8023.                                                    
  8024.      00  10  20  30  40  50  60  70  80  90  A0  B0  C0  D0  E0  F0
  8025.  
  8026.  
  8027.  Figure 10:  The kana-kanji conversion process under Windows Version 1.03.
  8028.  
  8029.                            ┌─────────────────────────────┐
  8030.                            │       Keyboard Drivers      │
  8031.       ░░ Level 1           │                             │
  8032.                            └─▒──▓──────────────────────░─┘
  8033.       ▒▒ Level 2             ▒  ▓                      ░
  8034.                            ┌──────────────────────────┐
  8035.       ▓▓ Level 3           │           USER.EXE          │
  8036.                            │               ▓▓▓▓▓▓▓      │
  8037.                            └─▒──▓──────────▓─▒▒▒─▓────░─┘
  8038.                            Keyboard        ▓ ▒  ▒ ▓    ░
  8039.                            hook set       Conversion   ░
  8040.                              ▒  ▓          mode off    ░
  8041.                        ┌───────────┐     ▓ ▒  ▒ ▓    No
  8042.                        │    KKLIB    ▓▓▓▓▓▓▓ ▒  ▒ ▓ keyboard
  8043.                        │             ▒▒▒▒▒▒▒▒▒  ▒ ▓   hook
  8044.                        └─────▒──▓────┘          ▒ ▓    ░
  8045.                              ▒  ▓               ▒ ▓    ░
  8046.    ┌─────────────┐     ┌───────────┐          ▒ ▓    ░
  8047.    │     FEP     │───│    KKAPP    │          ▒ ▓    ░
  8048.    │             │     │             │          ▒ ▓    ░
  8049.    └─────────────┘     └─────▒──▓───┘          ▒ ▓    ░
  8050.                              ▒  ▓   ▓           ▒ ▓    ░
  8051.                        Conversion   Virtual     ▒ ▓    ░
  8052.                            result   kanji       ▒ ▓    ░
  8053.                              ▒  ▓   key         ▒ ▓    ░
  8054.                              ▒  ▓   interface   ▒ ▓    ░
  8055.                              ▒  ▓   ▓           ▒ ▓    ░
  8056.                              Conversion request and    ░
  8057.                              ▒  ▓ results back  ▒ ▓    ░
  8058.                              ▒  ▓   ▓           ▒ ▓    ░
  8059.                           ╔═══════▓═══════════════════╗
  8060.                           ║          Applications          ║
  8061.                           ║                                ║
  8062.                           ╚════════════════════════════════╝
  8063.  
  8064.  ████████████████████████████████████████████████████████████████████████████
  8065.  
  8066.  Utilizing OS/2 Multithread Techniques in Presentation Manager Applications
  8067.  
  8068.  Charles Petzold
  8069.  
  8070.  Microsoft(R) Windows is a multitasking operating environment──but not really.
  8071.  As most Windows programmers know, Windows is actually a "nonpreemptive
  8072.  multitasking" environment. It does not perform the preemptive time-
  8073.  slicing we normally associate with a multitasking system. Instead, Windows
  8074.  multitasks programs based on the presence of "messages" (which often
  8075.  represent keyboard and mouse input) in the programs' message queues.
  8076.  
  8077.  When a Windows program calls the GetMessage function to retrieve the next
  8078.  message from its message queue, and the message queue is empty, Windows
  8079.  suspends the program. Windows then switches to another program with a
  8080.  nonempty message queue. This causes that other program to return from its
  8081.  own GetMessage call to process the message. Only one Windows program is
  8082.  running at any time. The rest are suspended in the GetMessage function.
  8083.  
  8084.  Windows programmers are well aware of the problems associated with this
  8085.  form of nonpreemptive multitasking. If a Windows program requires a long
  8086.  period of time to process a message, then other programs running under
  8087.  Windows are effectively halted for the duration. Windows programmers must
  8088.  use some special techniques (which we'll look at in this article) when
  8089.  doing lengthy processing to prevent the program from suspending the rest of
  8090.  the system.
  8091.  
  8092.  Microsoft OS/2 Presentation Manager is a windowing environment with a
  8093.  message-based architecture much like that of Microsoft Windows. But unlike
  8094.  Windows, the OS/2 Presentation Manager runs under a true priority-based
  8095.  preemptive multitasking operating system.
  8096.  
  8097.  At first, the preemptive multitasking of the OS/2 systems would seemingly
  8098.  eliminate the problems associated with the nonpreemptive nature of
  8099.  Windows. You might conclude that Presentation Manager programs can spend
  8100.  as much time as they need processing a message without worrying about
  8101.  suspending other programs. But this is not so. The problems that arise
  8102.  when doing lengthy processing in Windows result more from the message-based
  8103.  architecture rather than from nonpreemptive multitasking. In that
  8104.  sense, the Presentation Manager is the same as Windows.
  8105.  
  8106.  The real difference is that the Presentation Manager provides more and
  8107.  better solutions to the problem of lengthy processing. That's what we'll
  8108.  explore here.
  8109.  
  8110.  
  8111.  The "Big Job" Problem
  8112.  
  8113.  Presentation Manager programs can usually process most keyboard and mouse
  8114.  input very quickly. In a word processing program, for example, a
  8115.  character typed from the keyboard need only be inserted into the document
  8116.  and displayed on the screen. But many programs must also carry out commands
  8117.  that require more lengthy processing. My term for this lengthy
  8118.  processing is the "big job."
  8119.  
  8120.  In a Presentation Manager spreadsheet program, the big job is a
  8121.  recalculation or the execution of a long macro. In a database program, the
  8122.  big job is a file sort or indexing. In a word processing program, it's a
  8123.  pagination or spelling check. In a CAD program, it's redrawing the
  8124.  screen. In a communications program, it's reading the serial port when an
  8125.  incoming character is not immediately available. In most any
  8126.  Presentation Manager program, printing is a big job.
  8127.  
  8128.  A big job is anything that takes more than 1/10 second. This is based on
  8129.  the recommendation in the Presentation Manager documentation that
  8130.  programs take no more than 1/10 second to process a message. Although
  8131.  this is a guideline rather than a hard-and-fast rule, I'll refer to it here
  8132.  as the "1/10 second rule."
  8133.  
  8134.  To explore the big job problem, let's write a Presentation Manager
  8135.  program that does a big job-specifically, a calculation called the
  8136.  "savage" benchmark that is often used to test floating-point speed. The
  8137.  program will allow repeating the savage calculation 10, 100, 1000, or 10,000
  8138.  times depending on a menu selection.
  8139.  
  8140.  I'll use the "alternate" floating-point library in this program so that the
  8141.  calculation isn't affected by the presence of an 80287 math coprocessor.
  8142.  The program will have menu options to start the calculation running and-if
  8143.  possible-to abort it before it's finished. The 10,000 repetitions of the
  8144.  savage benchmark calculation takes about 3 minutes on an 8-MHz IBM(R) PC/AT(R
  8145.  Even 10 repetitions violate the 1/10 second rule.
  8146.  
  8147.  Five different versions of this program will be examined, with each
  8148.  successive version more sophisticated in its approach to multitasking under
  8149.  the Presentation Manager.
  8150.  
  8151.  
  8152.  The Naive Approach
  8153.  
  8154.  The simplest approach to a big job is shown in the BIGJOB1 program in
  8155.  Figures 1 and 2. Most of the code in BIGJOB1's client window procedure,
  8156.  which is called ClientWndProc, handles WM_COMMAND messages from the
  8157.  program's menu. When you select an option from the Repetitions menu, BIGJOB
  8158.  unchecks the currently selected option and checks the option you choose.
  8159.  When you select Start from the menu, BIGJOB1 disables the Start option,
  8160.  enables the Abort option, and begins the calculation. After it's finished
  8161.  with the big job, the program reenables the Start option and then exits the
  8162.  window procedure.
  8163.  
  8164.  Because BIGJOB1 does the entire calculation in response to a WM_COMMAND
  8165.  message from the menu, it obviously violates the 1/10 second rule. BIGJOB1
  8166.  is a bad program. I show it only to illustrate how such programs clog up the
  8167.  rest of the Presentation Manager.
  8168.  
  8169.  While BIGJOB1 is doing the big job, you cannot switch to another program
  8170.  running under the Presentation Manager. The system seemingly ignores all
  8171.  keyboard and mouse input until the calculation is finished. Although the
  8172.  Abort option is present on BIGJOB1's menu, you can't use the keyboard or
  8173.  mouse to select that option. Once you begin the big job, you have to wait
  8174.  until it's finished to be able to do anything else.
  8175.  
  8176.  At first, this seems disturbing. Isn't the Presentation Manager supposed to
  8177.  be multitasking? And if so, why does one program apparently cause the whole
  8178.  system to grind to a halt? What is happening here is a predictable result of
  8179.  the message-based architecture of the Presentation Manager.
  8180.  
  8181.  
  8182.  Using Messages
  8183.  
  8184.  Programs that run under the Presentation Manager are message driven. In the
  8185.  normal case, a program spends most of its time suspended in WinGetMsg (the
  8186.  Presentation Manager equivalent of the Windows GetMessage function)
  8187.  waiting for a message.
  8188.  
  8189.  Each window created by a program has a window procedure that processes
  8190.  messages to the window. At least one of these window procedures──the
  8191.  window procedure for the client window──is located in the Presentation
  8192.  Manager program. The other window procedures (such as those for the frame
  8193.  window, the title bar window, and the menu window) can be found in the
  8194.  Presentation Manager PMWIN.DLL dynamic-link library.
  8195.  
  8196.  Some of the messages for a window are stored in the program's message
  8197.  queue. These messages are called "queued" messages and are sometimes said to
  8198.  be "posted" to the queue. The queued messages are retrieved from the message
  8199.  queue when a program calls WinGetMsg and are dispatched to the window
  8200.  procedure by WinDispatchMsg. Other messages are sent directly to the window
  8201.  procedure, bypassing the program's message queue.
  8202.  
  8203.  Regardless of whether a message is posted to a message queue or sent
  8204.  directly to a window procedure, and regardless of whether a window
  8205.  procedure is contained in the program or in a dynamic-link library,
  8206.  messages to windows created by a thread of execution are always processed
  8207.  in that thread. A particular thread of execution can only do one thing at
  8208.  a time. A thread cannot be multitasked with itself. This seems obvious, yet
  8209.  the message-based architecture of the Presentation Manager sometimes
  8210.  obscures this simple fact.
  8211.  
  8212.  When you select Start from BIGJOB1's menu, the program begins the big job.
  8213.  You then try to use the Alt-Tab key combination to switch to another
  8214.  program. The window that must process these keystrokes is BIGJOB1's frame
  8215.  window. But the window procedure for the frame window must run in the same
  8216.  thread as the client window, and the client window is busy doing the big
  8217.  job. This means that the Alt-Tab keyboard message cannot be processed
  8218.  until the calculation is finished, and until BIGJOB1 exits ClientWndProc and
  8219.  then calls WinGetMsg to retrieve the message from the queue. This explains
  8220.  why the Presentation Manager seems to be ignoring keyboard input while
  8221.  BIGJOB1 is calculating.
  8222.  
  8223.  
  8224.  Serialization of Input
  8225.  
  8226.  There may, however, be a way around this. As you know, you can also use the
  8227.  mouse to make another window active. Maybe that will work.
  8228.  
  8229.  To test this possibility, you must first position the mouse pointer on top
  8230.  of another program's window and then use the keyboard to select Start from
  8231.  BIGJOB1's menu. While it is calculating, you press the mouse button and...
  8232.  nothing happens.
  8233.  
  8234.  This again is initially disturbing. Because the Presentation Manager is a
  8235.  true multitasking system, the other program ought to be able to read that
  8236.  mouse click even while BIGJOB1 is calculating. The Presentation Manager
  8237.  should also be able to change the active window from BIGJOB1 to the other
  8238.  program.
  8239.  
  8240.  This does not happen for a couple of reasons. First, the Presentation
  8241.  Manager serializes all keyboard and mouse input in a system message queue.
  8242.  Keyboard and mouse input is passed one message at a time to an
  8243.  application's message queue based on which window has the input focus (in
  8244.  the case of keyboard messages) and which window is underneath the mouse
  8245.  pointer.
  8246.  
  8247.  The serialization of mouse and keyboard input in a system message queue is
  8248.  required to correctly handle "type-ahead" and "mouse-ahead" input from the
  8249.  user. One of the keystrokes or mouse clicks in the system message queue
  8250.  could have the effect of changing the active window and the focus window.
  8251.  Subsequent keyboard input must then go to that new window. This won't work
  8252.  if mouse and keyboard input is not passed to applications in the same
  8253.  order that it enters the system message queue. Therefore, a keyboard or
  8254.  mouse message cannot be passed to a particular application until all
  8255.  previous keyboard and mouse messages have been processed.
  8256.  
  8257.  In this particular case, another application cannot read a mouse message
  8258.  until BIGJOB1 processes all its keyboard input. The keyboard input that it
  8259.  hasn't processed is the release of the key that caused the menu to send the
  8260.  WM_COMMAND message that started the calculation.
  8261.  
  8262.  Thus, because BIGJOB1 renders itself resistent to keyboard or mouse input,
  8263.  it also prevents all other programs running under the Presentation Manager
  8264.  from getting keyboard or mouse input.
  8265.  
  8266.  Even if another program could read a mouse click, the Presentation Manager
  8267.  cannot change the input focus from BIGJOB1 to another program while BIGJOB1
  8268.  is busy doing the calculation. In order to change the input focus, the
  8269.  Presentation Manager must send a WM_SETFOCUS message to the window losing
  8270.  the input focus. That WM_SETFOCUS message is blocked because the window that
  8271.  must receive the message is part of BIGJOB1's thread, and BIGJOB1 is busy
  8272.  doing the big job.
  8273.  
  8274.  Messages are not like hardware interrupts. Although a window procedure can
  8275.  be sent a message as a result of calling WinDefWindowProc, and a window
  8276.  procedure can be sent a message as a result of calling some other
  8277.  Presentation Manager functions, these are examples of recursion in window
  8278.  procedures. Messages do not preemptively interrupt a thread and start
  8279.  execution someplace else in the same thread.
  8280.  
  8281.  Now that we've seen how BIGJOB1 effectively disables keyboard and mouse
  8282.  input in the Presentation Manager, the reason for the 1/10 second rule
  8283.  should be obvious. Presentation Manager programs must continually interact
  8284.  with the system, retrieving and processing their messages promptly.
  8285.  
  8286.  
  8287.  Not Exactly Like Windows
  8288.  
  8289.  As bad as BIGJOB1 is, the Presentation Manager can still multitask programs
  8290.  while BIGJOB1 is running. This is a difference between the Presentation
  8291.  Manager and Windows.
  8292.  
  8293.  If you have a Windows version of BIGJOB1 running under Windows along with
  8294.  the Windows CLOCK program, CLOCK will stop dead while BIGJOB1 is
  8295.  calculating. Under Windows, only one program can be running at any time.
  8296.  
  8297.  However, when you run BIGJOB1 in the Presentation Manager along with a
  8298.  Presentation Manager version of CLOCK (like the one included in my book,
  8299.  Programming the OS/2 Presentation Manager, Microsoft Press, 1988) you'll
  8300.  find that CLOCK continues to run while BIGJOB1 is calculating. CLOCK and
  8301.  BIGJOB1 run concurrently.
  8302.  
  8303.  CLOCK works by setting a timer and then processing a WM_TIMER message every
  8304.  second. A WM_TIMER message does not have to be serialized with the
  8305.  keyboard and mouse input. CLOCK can continue to receive these messages
  8306.  even if BIGJOB1 has clogged up keyboard and mouse input.
  8307.  
  8308.  
  8309.  Using a Timer
  8310.  
  8311.  Once you acknowledge that BIGJOB1 is a very bad Presentation Manager
  8312.  program, you are stuck with the problem of structuring it so that it works
  8313.  correctly. The use of a timer in CLOCK suggests one possible remedy: you can
  8314.  divide a big job up into little pieces, set the Presentation Manager
  8315.  timer, and do each little piece on receipt of a WM_TIMER message. This is a
  8316.  solution that works both in the Presentation Manager and in Windows.
  8317.  
  8318.  The BIGJOB2 program uses a timer to do the big job. The files BIGJOB2,
  8319.  BIGJOB2.C, and BIGJOB2.DEF are shown in Figure 3. Compiling BIGJOB2 also
  8320.  requires the BIGJOB.H and BIGJOB.RC files shown in Figure 1.
  8321.  
  8322.  When you select the Start option from BIGJOB2's menu, BIGJOB2 calls
  8323.  WinStartTimer to start the timer, disables the Start option, enables the
  8324.  Abort option, and initializes a few variables. The savage calculation is
  8325.  done once for each WM_TIMER message. Thus, for 1000 repetitions, the big
  8326.  job is finished after 1000 WM_TIMER messages.
  8327.  
  8328.  WM_TIMER messages are low-priority queued messages. If the message queue
  8329.  contains keyboard or mouse messages, these messages will be retrieved from
  8330.  the queue and processed before a WM_TIMER message. Thus, BIGJOB2 continues
  8331.  to read keyboard and mouse input and can allow the user to select Abort from
  8332.  BIGJOB2's menu, move or resize BIGJOB2's window, or to move to another
  8333.  program. The entire system-including BIGJOB2-continues to function normally
  8334.  all the time that BIGJOB2 is doing the calculation.
  8335.  
  8336.  
  8337.  Timer Problems
  8338.  
  8339.  The timer approach is not too bad for BIGJOB2, but it's easy to imagine
  8340.  cases where the timer would be inadequate.
  8341.  
  8342.  A program using the timer for a big job must continually reenter and leave
  8343.  the processing loop with every WM_TIMER message. This is easy to structure
  8344.  when a single loop is involved, but it becomes a nightmare for more complex
  8345.  jobs with lots of nested loops.
  8346.  
  8347.  The timer also slows down the big job. It is not possible to receive
  8348.  WM_TIMER messages at a rate faster than that of the hardware clock. Under
  8349.  OS/2, this means the program receives a WM_TIMER message only once every
  8350.  31.25 milliseconds. But BIGJOB2 spends less than 20 milliseconds (on an 8-
  8351.  MHz IBM PC/AT) processing each WM_TIMER message. Because the calculation is
  8352.  paced by the timer, the calculation won't finish any faster on a 20-MHz
  8353.  80386 machine.
  8354.  
  8355.  Clearly, as a general solution to the big job program, the timer approach
  8356.  must be rejected.
  8357.  
  8358.  
  8359.  The Peek Message
  8360.  
  8361.  Another solution that is very common in Windows is the use of the
  8362.  PeekMessage function (called WinPeekMsg in the Presentation Manager). The
  8363.  PeekMessage function is very similar to GetMessage. Under Windows, the
  8364.  PeekMessage function first checks if there are messages waiting in the
  8365.  program's message queue. If so, PeekMessage fills the message structure
  8366.  with the next message from the queue and returns a non-zero value.
  8367.  
  8368.  If the message queue of the program calling PeekMessage is empty, then
  8369.  Windows switches to a program with a nonempty message queue to allow that
  8370.  program to process its own messages. (This is often called "yielding" to
  8371.  other programs.) When no messages remain in any program's message queues,
  8372.  then PeekMessage returns control to the original program and returns a zero
  8373.  value.
  8374.  
  8375.  PeekMessage allows all Windows programs to process outstanding messages
  8376.  but then returns control to the program calling PeekMessage after all this
  8377.  message processing is completed. Using PeekMessage allows a program to do a
  8378.  big job while periodically allowing itself and other programs to process
  8379.  their messages. Several Windows programs, including SPOOLER and TERMINAL,
  8380.  use PeekMessage in this way in order to simulate multitasking. Programs
  8381.  that print frequently use PeekMessage to allow a user to cancel a print job
  8382.  from a dialog box.
  8383.  
  8384.  The WinPeekMsg function in the Presentation Manager is very similar to
  8385.  PeekMessage. However, in the Presentation Manager, WinPeekMsg is used by a
  8386.  program to process its own messages rather than to yield to other programs.
  8387.  Multitasking occurs normally in the Presentation Manager if a program does
  8388.  not clog up the processing of keyboard and mouse input. The WinPeekMsg
  8389.  function provides a good solution to the big job problem. BIGJOB3 in
  8390.  Figure 4 shows how this is done.
  8391.  
  8392.  Like BIGJOB2, BIGJOB3 does the full calculation in response to a WM_COMMAND
  8393.  message. However, within the calculation loop, BIGJOB3 calls WinPeekMsg to
  8394.  determine if any messages are in its message queue. If so, it removes them
  8395.  with WinGetMsg and dispatches them to a window procedure with
  8396.  WinDispatchMsg, just as in the normal message loop in main.
  8397.  
  8398.  One of these messages could be another WM_COMMAND message generated when the
  8399.  user selects Abort from the menu. BIGJOB3 uses the bContinueCalc variable to
  8400.  indicate when the calculation has been aborted.
  8401.  
  8402.  You'll notice that some special processing is required for the WM_QUIT
  8403.  message. This message is posted to the message queue by the Presentation
  8404.  Manager as a default response when the user selects Close from the system
  8405.  menu. The WM_QUIT message should not be removed from the queue within the
  8406.  window procedure. Instead, BIGJOB3 exits the window procedure so that the
  8407.  WM_QUIT message can be retrieved from the message queue in the main
  8408.  function.
  8409.  
  8410.  Although message peeking usually works well in Windows and Presentation
  8411.  Manager programs, it's always a little messy in practice. The PeekMessage
  8412.  and WinPeekMsg functions must be called frequently enough to give the
  8413.  system a good response time, yet there's an inordinate amount of code
  8414.  involved. If the big job must be aborted, it's sometimes difficult to get
  8415.  out of a calculation loop in a structured manner. In short, code involving
  8416.  WinPeekMsg often looks like a kludge.
  8417.  
  8418.  
  8419.  A Second Thread
  8420.  
  8421.  The solutions in BIGJOB2 and BIGJOB3 are applicable in both Windows and the
  8422.  Presentation Manager. Now let's do something that is impossible in Windows
  8423.  but a natural in the Presentation Manager: create a second thread of
  8424.  execution to do the big job.
  8425.  
  8426.  When a program contains multiple threads of execution, the multiple threads
  8427.  run concurrently. All threads in a process share the program's
  8428.  resources──such as open files, memory, semaphores, and queues──but each
  8429.  thread has its own CPU state, dispatching priority, and stack.
  8430.  
  8431.  Within a program, a second thread of execution looks like a function. All
  8432.  local automatic variables in the thread function are private to the thread
  8433.  because they are stored on the thread's stack. Local static variables in the
  8434.  thread function can be shared by all threads based on that same function.
  8435.  
  8436.  When programming for the Presentation Manager, threads fall into two
  8437.  categories: a thread is either a "message queue thread" or a "nonmessage
  8438.  queue thread." A thread becomes a message queue thread when it calls
  8439.  WinCreateMsgQueue. It reverts back to being a nonmessage queue thread when
  8440.  it calls WinDestroyMsgQueue.
  8441.  
  8442.  A Presentation Manager program always creates a message queue in at least
  8443.  one thread. A thread must create a message queue before it can create
  8444.  windows. The message queue is used to store messages for all of the windows
  8445.  created in the thread. However, other threads in a Presentation Manager
  8446.  program do not need to create message queues if they do not create
  8447.  windows.
  8448.  
  8449.  
  8450.  Thread Restrictions
  8451.  
  8452.  In a Presentation Manager program, nonmessage queue threads have some
  8453.  advantages over message queue threads──and some disadvantages.
  8454.  
  8455.  The good news is that a nonmessage queue thread is not bound by the 1/10
  8456.  second rule. Because the thread does not receive or process messages, it
  8457.  needn't worry about clogging up the processing of messages in message queue
  8458.  threads. Thus, a nonmessage queue thread is often ideal for doing big jobs.
  8459.  
  8460.  The bad news is that nonmessage queue threads are restricted in the
  8461.  Presentation Manager functions they may call. Basically, a thread without a
  8462.  message queue cannot create windows, cannot send a message to a window
  8463.  procedure in a message queue thread, and cannot call any function that
  8464.  causes a message to be sent to a window procedure.
  8465.  
  8466.  Some of these restrictions are obvious: a nonmessage queue thread cannot
  8467.  create a window because it has no queue to store messages to that window.
  8468.  However, a nonmessage queue thread can call some functions that affect
  8469.  windows created in message queue threads. For example, a nonmessage queue
  8470.  thread can obtain a presentation space handle for a window created in a
  8471.  message queue thread and can paint something on the surface of that window.
  8472.  
  8473.  Nonmessage queue threads cannot send messages to message queue threads. The
  8474.  WinSendMsg function is not allowed. Nor can they call functions that send
  8475.  messages. For example, WinDestroyWindow cannot be called from a nonmessage
  8476.  queue thread because it sends a window procedure a WM_DESTROY message. The
  8477.  functions that are forbidden to nonmessage queue threads are listed in the
  8478.  Presentation Manager documentation.
  8479.  
  8480.  Although a nonmessage queue thread cannot send a message using WinSendMsg,
  8481.  the thread can post a message by calling WinPostMsg. This latter function
  8482.  places the message in a thread's message queue and returns immediately.
  8483.  
  8484.  
  8485.  Semaphores
  8486.  
  8487.  Threads within a single process must often communicate with each other in
  8488.  various ways. The execution of threads must be coordinated so that the
  8489.  threads don't step on each other's toes. This requires some handshaking.
  8490.  Often, threads must also signal each other and pass data among themselves.
  8491.  
  8492.  A message queue thread generally communicates with a nonmessage queue
  8493.  thread by using semaphores. A nonmessage queue thread communicates with
  8494.  a message queue thread by using posted messages. Both threads can also
  8495.  access common global variables.
  8496.  
  8497.  The BIGJOB4 program in Figure 5, which uses a nonmessage queue thread to do
  8498.  a big job, shows this signaling and communication.
  8499.  
  8500.  The global variable lSemTrigger is a RAM semaphore. This semaphore is set
  8501.  during the WM_CREATE message in ClientWndProc. The window procedure then
  8502.  creates a second thread by calling the OS/2 DosCreateThread function using
  8503.  SecondThread as the thread function.
  8504.  
  8505.  SecondThread waits for lSemTrigger to be cleared. ClientWndProc clears the
  8506.  semaphore when the user selects Start from BIGJOB4's menu. SecondThread then
  8507.  does the calculation. When it's finished, it posts a WM_CALC_DONE message to
  8508.  the window procedure. (This is a "user-defined" message defined within
  8509.  BIGJOB4.C.) The elapsed time of the calculation is passed to the window
  8510.  procedure in the lParam1 variable that accompanies this message.
  8511.  
  8512.  
  8513.  Aborting the Calculation
  8514.  
  8515.  BIGJOB4 also allows the user to abort the calculation. When Abort is
  8516.  selected from the menu, the window procedure sets the bContinueCalc global
  8517.  variable to FALSE and disables the Abort menu option.
  8518.  
  8519.  SecondThread checks this bContinueCalc variable during the calculation and
  8520.  breaks from the loop when the variable is no longer non-zero. The thread
  8521.  then sets the semaphore and posts a WM_CALC_ABORTED to the window. On
  8522.  receipt of this message, the client window enables the Start menu option
  8523.  again. The semaphore is already set, so SecondThread can't proceed with a
  8524.  new calculation until Start is chosen again. This is an example of some
  8525.  simple handshaking between the two threads.
  8526.  
  8527.  Note that the semaphore is used only for blocking and unblocking the
  8528.  nonmessage queue thread. A message queue thread should not be made to wait
  8529.  on a semaphore because that may violate the 1/10 second rule. If absolutely
  8530.  necessary, a nonmessage queue thread could suspend a message queue thread
  8531.  for very short periods of time by calling DosSuspendThread or
  8532.  DosEnterCritSec. This is sometimes helpful when both threads access global
  8533.  variables. (It's not necessary in BIGJOB4 when the threads access
  8534.  bContinueCalc because this variable is altered or accessed in one machine
  8535.  code instruction.)
  8536.  
  8537.  The message queue thread in the BIGJOB4 program calls DosSuspendThread to
  8538.  suspend SecondThread after exiting the message loop in main before
  8539.  destroying the window and message queue. SecondThread could be in the middle
  8540.  of a calculation at this time. Suspending the thread prevents it from
  8541.  posting a message to the message queue after the message queue is
  8542.  destroyed.
  8543.  
  8544.  
  8545.  Thinking Threads
  8546.  
  8547.  A nonmessage queue thread is nearly essential in Presentation Manager
  8548.  programs that must read input other than the keyboard and the mouse. An
  8549.  obvious example of this is a communications program. Such a program would
  8550.  have a client window in the message queue thread that processes keyboard
  8551.  messages, writes the characters to the communications port using DosWrite,
  8552.  and (if local echo is in effect) also writes the characters to the surface
  8553.  of the window.
  8554.  
  8555.  The nonmessage queue thread reads the communications port with the DosRead
  8556.  function. Used most efficiently, this function does not return unless a
  8557.  character has been read from the serial port. A message queue thread should
  8558.  not call DosRead to get input from the serial port because it may violate
  8559.  the 1/10 second rule. When the nonmessage queue thread reads a character, it
  8560.  can post a user-defined message to the window. The client window processes
  8561.  the message by displaying the character to the window.
  8562.  
  8563.  A Presentation Manager program using queues for interprocess communication
  8564.  should also create a nonmessage queue thread for reading the queue. The
  8565.  nonmessage queue thread calls the DosReadQueue function with the "no wait"
  8566.  flag set to 0, thus blocking the thread until there is something in the
  8567.  queue.
  8568.  
  8569.  In BIGJOB4, the second thread is created once and unblocked whenever it has
  8570.  to do the big job. If a program has numerous big jobs handled in lots of
  8571.  different thread functions, it would be best not to create all these
  8572.  threads initially, but to create a thread each time it is needed. When a
  8573.  thread is finished with its big job, it can post a message to the client
  8574.  window and terminate using the DosExit call with a first parameter of 0.
  8575.  
  8576.  
  8577.  Object Windows
  8578.  
  8579.  BIGJOB4 shows how a program can handle big jobs in a nonmessage queue
  8580.  thread. A Presentation Manager program can also do big jobs in a second
  8581.  message queue thread that creates "object windows."
  8582.  
  8583.  Object windows are created in a message queue thread just like normal
  8584.  windows. They have a window procedure that processes messages to the object
  8585.  window, again just like normal windows. However, object windows do not
  8586.  appear on the screen and do not get keyboard and mouse input. In fact,
  8587.  object windows basically receive only two messages: WM_CREATE and
  8588.  WM_DESTROY. The WM_CREATE message is sent during the WinCreateWindow call
  8589.  that creates the object window, and the WM_DESTROY message is sent during
  8590.  the WinDestroyWindow call that destroys the window.
  8591.  
  8592.  (In the pre-release version of the Presentation Manager I have, object
  8593.  windows also receive a WM_ADJUSTWINDOWPOS message concurrently with the
  8594.  WinCreateWindow call.)
  8595.  
  8596.  You normally use object windows specifically to receive messages from other
  8597.  windows, perhaps to implement an object-oriented programming language. But
  8598.  object windows also help out in processing big jobs. If a thread creates
  8599.  only object windows, then there are only a very limited number of messages
  8600.  the window procedure can receive. This greatly simplifies the processing
  8601.  of these messages in the object window's window procedure.
  8602.  
  8603.  There are several advantages to using object windows for big jobs. The
  8604.  object window is not restricted to a subset of Presentation Manager
  8605.  functions. You can thus use messages for all communications between the
  8606.  client window and the object window. If you've come to regard message-driven
  8607.  architecture as inherently superior to traditional top-down architecture,
  8608.  you may prefer this. The use of object windows in separate threads is more
  8609.  in tune with the architecture of the Presentation Manager than are simple
  8610.  nonmessage queue threads.
  8611.  
  8612.  The use of an object window in a second message queue thread is shown in the
  8613.  BIGJOB5 program in Figure 6. The BIGJOB5.C file defines six messages that
  8614.  the client window and object window use for communicating with each other.
  8615.  
  8616.  BIGJOB5 generally creates the second thread after creating the program's
  8617.  main window. BIGJOB5's SecondThread function looks a lot like the main
  8618.  function. Although it does not call WinInitialize, it registers a window
  8619.  class and creates a window. The WinCreateWindow call to create an object
  8620.  window is very simple: the parent window parameter is set to HWND_OBJECT,
  8621.  the second parameter is set to the name of the window class, and all other
  8622.  parameters are set to 0 or NULL.
  8623.  
  8624.  SecondThread then sends a WM_OBJECT_CREATED message to ClientWndProc to
  8625.  notify it that the object window has been created and is ready for work.
  8626.  SecondThread then enters a normal message loop.
  8627.  
  8628.  When you select Start from BIGJOB5's menu, the client window posts the
  8629.  message WM_START_CALC to the object window. Similarly, selecting Abort
  8630.  from BIGJOB5's menu causes the client window to post a WM_ABORT_CALC
  8631.  message. Note as well that ClientWndProc processes the normal WM_CLOSE
  8632.  message (generated by selecting Close from the system menu) by posting a
  8633.  WM_QUIT message to the object window.
  8634.  
  8635.  ObjectWndProc begins the big job in response to the WM_START_CALC message.
  8636.  While doing the calculation, it can be interrupted only by one of two
  8637.  possible messages posted to its message queue. These messages are
  8638.  WM_ABORT_CALC and WM_QUIT. In either case, the calculation can be
  8639.  unconditionally aborted.
  8640.  
  8641.  In the calculation loop, ObjectWndProc calls the WinQueryQueueStatus
  8642.  function and checks the QS_POSTMSG bit of the return value. (It should be
  8643.  necessary only to check that the return value is non-zero, but in the
  8644.  version of the Presentation Manager I used, this function was a little
  8645.  buggy.) This check of WinQueryQueueStatus is almost as simple as checking
  8646.  bContinueCalc in BIGJOB4, but not nearly as complex as the code involved
  8647.  with using WinPeekMsg in BIGJOB3.
  8648.  
  8649.  ObjectWndProc notifies ClientWndProc that it has finished or aborted a
  8650.  calculation by posting the messages WM_CALC_DONE and WM_CALC_ABORTED
  8651.  respectively. ClientWndProc uses these messages to enable its menu for
  8652.  further calculations.
  8653.  
  8654.  Ending the program in an orderly manner (that is, where both main and
  8655.  SecondThread destroy their own windows and message queues) is a little
  8656.  tricky. ClientWndProc processes a WM_CLOSE message by posting a WM_QUIT
  8657.  message to the object window. This causes ObjectWndProc to abort the big job
  8658.  if it's doing one at the time. When this WM_QUIT message is retrieved
  8659.  from SecondThread's message queue, WinGetMsg returns 0. SecondThread
  8660.  destroys its window, its message queue, and posts a WM_OBJECT_DESTROYED
  8661.  message to ClientWndProc. On receipt of this message, ClientWndProc posts
  8662.  itself a WM_QUIT message and terminates normally.
  8663.  
  8664.  The use of an object window for big jobs is certainly more complex than the
  8665.  use of a nonmessage queue thread, so you may not like it. Moreover, the
  8666.  object window approach is not good for some big jobs. For example, when a
  8667.  second thread must use DosRead to get input from a communications port or
  8668.  DosReadQueue to get input from a queue, the thread is blocked within the
  8669.  OS/2 function and obviously cannot check the message queue status using
  8670.  WinQueryQueueStatus.
  8671.  
  8672.  
  8673.  No More "Please Wait"
  8674.  
  8675.  We started out looking at BIGJOB1, a program that did the job it was meant
  8676.  to do, but did it in a way that was not advantageous for the user. Our
  8677.  immediate rejection of this program and our search for better ways of doing
  8678.  big jobs indicates some major changes in our conception of proper behavior
  8679.  in application programs.
  8680.  
  8681.  In a traditional single-tasking nonwindowed environment, of course you have
  8682.  to wait while the database program is sorting a file. When you start a file
  8683.  sort, it's time to take a coffee break.
  8684.  
  8685.  In a traditional multitasking operating environment, you may be able to let
  8686.  the database program sort in the background, and you can work on another
  8687.  program while waiting for the sort to finish.
  8688.  
  8689.  In a multitasking window environment like the Presentation Manager,
  8690.  however, we are satisfied only when the user can continue to interact with a
  8691.  program even when it's doing a big job. Obviously, the complexities
  8692.  involved with structuring a program in this way require some extra work on
  8693.  the part of the programmer. But that makes the program better to use.
  8694.  
  8695.  Just as we can no longer tolerate programs that require the user to memorize
  8696.  scores of commands, we can no longer tolerate programs that throw up a
  8697.  "Please Wait" message and require a user to wait until the program finishes
  8698.  its big job.
  8699.  
  8700.  
  8701.  Figure 1:
  8702.  
  8703.  BIGJOB.RC resource script file
  8704.  
  8705.  #include <os2.h>
  8706.  #include "bigjob.h"
  8707.  
  8708.  MENU ID_RESOURCE
  8709.       {
  8710.       SUBMENU "~Repetitions",  IDM_REPS
  8711.            {
  8712.            MENUITEM "     10", IDM_10,    MIA_CHECKED
  8713.            MENUITEM "    100", IDM_100
  8714.            MENUITEM "   1000", IDM_1000
  8715.            MENUITEM "  10000", IDM_10000
  8716.            }
  8717.       SUBMENU "~Action",       IDM_ACTION
  8718.            {
  8719.            MENUITEM "~Start",  IDM_START
  8720.            MENUITEM "~Abort",  IDM_ABORT, MIA_DISABLED
  8721.            }
  8722.       }
  8723.  
  8724.  BIGJOB.H header file
  8725.  
  8726.  #define ID_RESOURCE 1
  8727.  
  8728.  #define IDM_REPS    1
  8729.  #define IDM_ACTION  2
  8730.  
  8731.  #define IDM_10      10
  8732.  #define IDM_100     100
  8733.  #define IDM_1000    1000
  8734.  #define IDM_10000   10000
  8735.  
  8736.  #define IDM_START   20
  8737.  #define IDM_ABORT   21
  8738.  
  8739.       /* Definitions, functions, and variables for BIGJOBx.C */
  8740.  
  8741.  #ifndef RC_INVOKED       /* This stuff not needed for .RC file */
  8742.  
  8743.  #define STATUS_READY     0
  8744.  #define STATUS_WORKING   1
  8745.  #define STATUS_DONE      2
  8746.  
  8747.  ULONG EXPENTRY ClientWndProc (HWND, USHORT, ULONG, ULONG) ;
  8748.  
  8749.  HAB  hab ;
  8750.  
  8751.  double Savage (double A)
  8752.       {
  8753.       return tan (atan (exp (log (sqrt (A * A))))) + 1.0 ;
  8754.       }
  8755.  
  8756.  VOID CheckMenuItem (HWND hwnd, SHORT iMenuItem, BOOL bCheck)
  8757.       {
  8758.       HWND hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
  8759.       HWND hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;
  8760.  
  8761.       WinSendMsg (hwndMenu, MM_SETITEMATTR, MAKEULONG (iMenuItem, TRUE),
  8762.                   MAKEULONG (MIA_CHECKED, bCheck ? MIA_CHECKED : 0)) ;
  8763.       }
  8764.  
  8765.  VOID EnableMenuItem (HWND hwnd, SHORT iMenuItem, BOOL bEnable)
  8766.       {
  8767.       HWND hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
  8768.       HWND hwndMenu   = WinWindowFromID (hwndParent, FID_MENU) ;
  8769.  
  8770.       WinSendMsg (hwndMenu, MM_SETITEMATTR, MAKEULONG (iMenuItem, TRUE),
  8771.                   MAKEULONG (MIA_DISABLED, bEnable ? 0 : MIA_DISABLED)) ;
  8772.       }
  8773.  
  8774.  VOID PaintWindow (HWND hwnd, SHORT iStatus, SHORT iRep, LONG lTime)
  8775.       {
  8776.       static CHAR *szMessage [3] = { "Ready", "Working ...",
  8777.                                      "%d repetitions in %ld msec." } ;
  8778.       CHAR        szBuffer [60] ;
  8779.       HPS         hps ;
  8780.       WRECT       wrc ;
  8781.  
  8782.       hps = WinBeginPaint (hwnd, NULL, NULL) ;
  8783.       GpiErase (hps) ;
  8784.  
  8785.       WinQueryWindowRect (hwnd, &wrc) ;
  8786.  
  8787.       sprintf (szBuffer, szMessage [iStatus], iRep, lTime) ;
  8788.  
  8789.       WinDrawText (hps, -1, szBuffer, &wrc, DT_CENTER | DT_VCENTER) ;
  8790.       WinEndPaint (hps) ;
  8791.       }
  8792.  
  8793.  #endif
  8794.  
  8795.  
  8796.  Figure 2:
  8797.  
  8798.  BIGJOB1 Make File
  8799.  
  8800.  bigjob1.obj : bigjob1.c bigjob.h
  8801.       cl -c -FPa -G2sw -W3 -Zp bigjob1.c
  8802.  
  8803.  bigjob.res : bigjob.rc bigjob.h
  8804.       rc -r bigjob.rc
  8805.  
  8806.  bigjob1.exe : bigjob1.obj bigjob1.def bigjob.res
  8807.       link bigjob1, /align:16, /map, /nod slibc slibcp slibfa os2,
  8808.       bigjob1 rc bigjob.res bigjob1.exe
  8809.  
  8810.  
  8811.  BIGJOB1.DEF Module Definition File
  8812.  
  8813.  NAME           BIGJOB1
  8814.  DESCRIPTION    'BIGJOB Demo Program No. 1(C) Charles Petzold, 1988'
  8815.  HEAPSIZE       1024
  8816.  STACKSIZE      8192
  8817.  EXPORTS        ClientWndProc
  8818.  
  8819.  BIGJOB1.C - Naive Approach to Lengthy Processing Job
  8820.  
  8821.  #define INCL_WIN
  8822.  
  8823.  #include <os2.h>
  8824.  #include <math.h>
  8825.  #include <stdio.h>
  8826.  #include "bigjob.h"
  8827.  
  8828.  INT main (VOID)
  8829.       {
  8830.       static CHAR szClassName [] = "BigJob1" ;
  8831.       HMQ         hmq ;
  8832.       HWND        hwndFrame, hwndClient ;
  8833.       QMSG        qmsg ;
  8834.  
  8835.       hab = WinInitialize (0) ;
  8836.       hmq = WinCreateMsgQueue (hab, 0) ;
  8837.  
  8838.       WinRegisterClass (hab, szClassName, ClientWndProc,
  8839.                              CS_SYNCPAINT | CS_SIZEREDRAW, 0, NULL) ;
  8840.  
  8841.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  8842.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  8843.                                 | FS_SYSMENU    | FS_MINMAX
  8844.                                 | FS_MENU,
  8845.                      szClassName, "BIGJOB Demo No. 1",
  8846.                      0L, NULL, ID_RESOURCE, &hwndClient) ;
  8847.  
  8848.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8849.            WinDispatchMsg (hab, &qmsg) ;
  8850.  
  8851.       WinDestroyWindow (hwndFrame) ;
  8852.       WinDestroyMsgQueue (hmq) ;
  8853.       WinTerminate (hab) ;
  8854.  
  8855.       return 0 ;
  8856.       }
  8857.  
  8858.  ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
  8859.                                ULONG mp2)
  8860.       {
  8861.       static SHORT iCalcRep, iCurrentRep = IDM_10 ;
  8862.       static SHORT iStatus = STATUS_READY ;
  8863.       static ULONG lElapsedTime ;
  8864.       double       A ;
  8865.       SHORT        i ;
  8866.  
  8867.       switch (msg)
  8868.            {
  8869.            case WM_COMMAND:
  8870.  
  8871.                 switch (LOUSHORT (mp1))
  8872.                      {
  8873.                      case IDM_10:
  8874.                      case IDM_100:
  8875.                      case IDM_1000:
  8876.                      case IDM_10000:
  8877.                           CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
  8878.                           iCurrentRep = LOUSHORT (mp1) ;
  8879.                           CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
  8880.                           break ;
  8881.  
  8882.                      case IDM_START:
  8883.                           EnableMenuItem (hwnd, IDM_START, FALSE) ;
  8884.                           EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
  8885.  
  8886.                           iStatus = STATUS_WORKING ;
  8887.                           WinInvalidateRect (hwnd, NULL) ;
  8888.  
  8889.                           iCalcRep = iCurrentRep ;
  8890.                           lElapsedTime = WinGetCurrentTime (hab) ;
  8891.  
  8892.                           for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
  8893.                                A = Savage (A) ;
  8894.  
  8895.                           lElapsedTime = WinGetCurrentTime (hab) -
  8896.                                          lElapsedTime ;
  8897.  
  8898.                           iStatus = STATUS_DONE ;
  8899.                           WinInvalidateRect (hwnd, NULL) ;
  8900.  
  8901.                           EnableMenuItem (hwnd, IDM_START, TRUE) ;
  8902.                           EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  8903.                           break ;
  8904.  
  8905.                      case IDM_ABORT:
  8906.  
  8907.                                /* Not much we can do here */
  8908.  
  8909.                           break ;
  8910.  
  8911.                      default:
  8912.                           break ;
  8913.                      }
  8914.                 break ;
  8915.  
  8916.            case WM_PAINT:
  8917.                 PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
  8918.                 break ;
  8919.  
  8920.            default:
  8921.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  8922.            }
  8923.       return 0L ;
  8924.       }
  8925.  
  8926.  
  8927.  Figure 3:
  8928.  
  8929.  BIGJOB2 Make File
  8930.  
  8931.  bigjob2.obj : bigjob2.c bigjob.h
  8932.       cl -c -FPa -G2sw -W3 -Zp bigjob2.c
  8933.  
  8934.  bigjob.res : bigjob.rc bigjob.h
  8935.       rc -r bigjob.rc
  8936.  
  8937.  bigjob2.exe : bigjob2.obj bigjob2.def bigjob.res
  8938.       link bigjob2, /align:16, /map, /nod slibc slibcp slibfa os2,
  8939.       bigjob2 rc bigjob.res bigjob2.exe
  8940.  
  8941.  BIGJOB2.DEF Module Definition File
  8942.  
  8943.  NAME           BIGJOB2
  8944.  DESCRIPTION    'BIGJOB Demo Program No. 2(C) Charles Petzold, 1988'
  8945.  HEAPSIZE       1024
  8946.  STACKSIZE      8192
  8947.  EXPORTS        ClientWndProc
  8948.  
  8949.  BIGJOB2.C - Timer Approach to Lengthy Processing Job
  8950.  
  8951.  #define INCL_WIN
  8952.  
  8953.  #include <os2.h>
  8954.  #include <math.h>
  8955.  #include <stdio.h>
  8956.  #include "bigjob.h"
  8957.  
  8958.  #define ID_TIMER 1
  8959.  
  8960.  INT main (VOID)
  8961.       {
  8962.       static CHAR szClassName [] = "BigJob2" ;
  8963.       HMQ         hmq ;
  8964.       HWND        hwndFrame, hwndClient ;
  8965.       QMSG        qmsg ;
  8966.  
  8967.       hab = WinInitialize (0) ;
  8968.       hmq = WinCreateMsgQueue (hab, 0) ;
  8969.  
  8970.       WinRegisterClass (hab, szClassName, ClientWndProc,
  8971.                              CS_SYNCPAINT | CS_SIZEREDRAW, 0, NULL) ;
  8972.  
  8973.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  8974.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  8975.                                 | FS_SYSMENU    | FS_MINMAX
  8976.                                 | FS_MENU,
  8977.                      szClassName, "BigJob Demo No. 2",
  8978.                      0L, NULL, ID_RESOURCE, &hwndClient) ;
  8979.  
  8980.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8981.            WinDispatchMsg (hab, &qmsg) ;
  8982.  
  8983.       WinDestroyWindow (hwndFrame) ;
  8984.       WinDestroyMsgQueue (hmq) ;
  8985.       WinTerminate (hab) ;
  8986.  
  8987.       return 0 ;
  8988.       }
  8989.  
  8990.  ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mpl,
  8991.                                ULONG mp2)
  8992.       {
  8993.       static double A ;
  8994.       static SHORT  i, iCalcRep, iCurrentRep = IDM_10 ;
  8995.       static SHORT  iStatus = STATUS_READY ;
  8996.       static ULONG  lElapsedTime;
  8997.  
  8998.       switch (msg)
  8999.            {
  9000.            case WM_COMMAND:
  9001.  
  9002.                 switch (LOUSHORT (mp1))
  9003.                      {
  9004.                      case IDM_10:
  9005.                      case IDM_100:
  9006.                      case IDM_1000:
  9007.                      case IDM_10000:
  9008.                           CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
  9009.                           iCurrentRep = LOUSHORT (mp1) ;
  9010.                           CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
  9011.                           break ;
  9012.  
  9013.                      case IDM_START:
  9014.  
  9015.                           if (!WinStartTimer (hab, hwnd, ID_TIMER, 1))
  9016.                                {
  9017.                                WinAlarm (HWND_DESKTOP, WA_ERROR) ;
  9018.                                break ;
  9019.                                }
  9020.  
  9021.                           EnableMenuItem (hwnd, IDM_START, FALSE) ;
  9022.                           EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
  9023.  
  9024.                           iStatus = STATUS_WORKING ;
  9025.                           WinInvalidateRect (hwnd, NULL) ;
  9026.  
  9027.                           iCalcRep = iCurrentRep ;
  9028.                           lElapsedTime = WinGetCurrentTime (hab) ;
  9029.  
  9030.                           A = 1.0 ;
  9031.                           i = 0 ;
  9032.  
  9033.                           break ;
  9034.  
  9035.                      case IDM_ABORT:
  9036.                           WinStopTimer (hab, hwnd, ID_TIMER) ;
  9037.  
  9038.                           iStatus = STATUS_READY ;
  9039.                           WinInvalidateRect (hwnd, NULL) ;
  9040.  
  9041.                           EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9042.                           EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9043.                           break ;
  9044.  
  9045.                      default:
  9046.                           break ;
  9047.                      }
  9048.                 break ;
  9049.  
  9050.            case WM_TIMER:
  9051.  
  9052.                 A = Savage (A) ;
  9053.  
  9054.                 if (++i == iCalcRep)
  9055.                      {
  9056.                      lElapsedTime = WinGetCurrentTime (hab) -
  9057.                                          lElapsedTime ;
  9058.  
  9059.                      WinStopTimer (hab, hwnd, ID_TIMER) ;
  9060.  
  9061.                      iStatus = STATUS_DONE ;
  9062.                      WinInvalidateRect (hwnd, NULL) ;
  9063.  
  9064.                      EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9065.                      EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9066.                      }
  9067.                 break ;
  9068.  
  9069.            case WM_PAINT:
  9070.                 PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
  9071.                 break ;
  9072.  
  9073.            default:
  9074.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9075.            }
  9076.       return 0L ;
  9077.       }
  9078.  
  9079.  
  9080.  Figure 4:
  9081.  
  9082.  BIGJOB3 Make File
  9083.  
  9084.  bigjob3.obj : bigjob3.c bigjob.h
  9085.       cl -c -FPa -G2sw -W3 -Zp bigjob3.c
  9086.  
  9087.  bigjob.res : bigjob.rc bigjob.h
  9088.       rc -r bigjob.rc
  9089.  
  9090.  bigjob3.exe : bigjob3.obj bigjob3.def bigjob.res
  9091.       link bigjob3, /align:16, /map, /nod slibc slibcp slibfa os2,
  9092.       bigjob3 rc bigjob.res bigjob3.exe
  9093.  
  9094.  BIGJOB3.DEF Module Definition File
  9095.  
  9096.  NAME           BIGJOB3
  9097.  DESCRIPTION    'BigJob Demo Program No. 3(C) Charles Petzold, 1988'
  9098.  HEAPSIZE       1024
  9099.  STACKSIZE      8192
  9100.  EXPORTS        ClientWndProc
  9101.  
  9102.  BIGJOB3.C - PeekMessage Approach to Lengthy Processing Job
  9103.  
  9104.  #define INCL_WIN
  9105.  
  9106.  #include <os2.h>
  9107.  #include <math.h>
  9108.  #include <stdio.h>
  9109.  #include "bigjob.h"
  9110.  
  9111.  INT main (VOID)
  9112.       {
  9113.       static CHAR szClassName [] = "BigJob3" ;
  9114.       HMQ         hmq ;
  9115.       HWND        hwndFrame, hwndClient ;
  9116.       QMSG        qmsg ;
  9117.  
  9118.       hab = WinInitialize (0) ;
  9119.       hmq = WinCreateMsgQueue (hab, 0) ;
  9120.  
  9121.       WinRegisterClass (hab, szClassName, ClientWndProc,
  9122.                              CS_SIZEREDRAW, 0, NULL) ;
  9123.  
  9124.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  9125.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  9126.                                 | FS_SYSMENU    | FS_MINMAX
  9127.                                 | FS_MENU,
  9128.                      szClassName, "BigJob Demo No. 3",
  9129.                      0L, NULL, ID_RESOURCE, &hwndClient) ;
  9130.  
  9131.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9132.            WinDispatchMsg (hab, &qmsg) ;
  9133.  
  9134.       WinDestroyWindow (hwndFrame) ;
  9135.       WinDestroyMsgQueue (hmq) ;
  9136.       WinTerminate (hab) ;
  9137.  
  9138.       return 0 ;
  9139.       }
  9140.  
  9141.  ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg,
  9142.                                ULONG mp1,ULONG mp2)
  9143.       {
  9144.       static BOOL   bContinueCalc = FALSE ;
  9145.       static SHORT  iStatus = STATUS_READY ;
  9146.       static SHORT  iCalcRep, iCurrentRep = IDM_10 ;
  9147.       static ULONG  lElapsedTime ;
  9148.       double        A ;
  9149.       SHORT         i ;
  9150.       QMSG          qmsg ;
  9151.  
  9152.  
  9153.       switch (msg)
  9154.            {
  9155.            case WM_COMMAND:
  9156.  
  9157.  
  9158.                 switch (LOUSHORT (mp1))
  9159.                      {
  9160.                      case IDM_10:
  9161.                      case IDM_100:
  9162.                      case IDM_1000:
  9163.                      case IDM_10000:
  9164.                           CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
  9165.                           iCurrentRep = LOUSHORT (mp1) ;
  9166.                           CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
  9167.                           break ;
  9168.  
  9169.                      case IDM_START:
  9170.                           EnableMenuItem (hwnd, IDM_START, FALSE) ;
  9171.                           EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
  9172.  
  9173.                           iStatus = STATUS_WORKING ;
  9174.                           WinInvalidateRect (hwnd, NULL) ;
  9175.  
  9176.                           iCalcRep = iCurrentRep ;
  9177.                           bContinueCalc = TRUE ;
  9178.                           lElapsedTime = WinGetCurrentTime (hab) ;
  9179.  
  9180.                           qmsg.msg = WM_NULL ;
  9181.  
  9182.                           for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
  9183.                                {
  9184.                                A = Savage (A) ;
  9185.  
  9186.                                while (WinPeekMsg (hab, &qmsg, NULL, 0, 0,
  9187.                                                    PM_NOREMOVE))
  9188.                                     {
  9189.                                     if (qmsg.msg == WM_QUIT)
  9190.                                          break ;
  9191.  
  9192.                                     WinGetMsg (hab, &qmsg, NULL, 0, 0) ;
  9193.                                     WinDispatchMsg (hab, &qmsg) ;
  9194.  
  9195.                                     if (!bContinueCalc)
  9196.                                          break ;
  9197.                                     }
  9198.  
  9199.                                if (!bContinueCalc || qmsg.msg == WM_QUIT)
  9200.                                     break ;
  9201.                                }
  9202.                           lElapsedTime = WinGetCurrentTime (hab) -
  9203.                                                    lElapsedTime ;
  9204.  
  9205.                           if (!bContinueCalc || qmsg.msg == WM_QUIT)
  9206.                                iStatus = STATUS_READY ;
  9207.                           else
  9208.                                iStatus = STATUS_DONE ;
  9209.  
  9210.                           WinInvalidateRect (hwnd, NULL) ;
  9211.  
  9212.                           EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9213.                           EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9214.                           break ;
  9215.  
  9216.                      case IDM_ABORT:
  9217.                           bContinueCalc = FALSE ;
  9218.                           break ;
  9219.  
  9220.                      default:
  9221.                           break ;
  9222.                      }
  9223.                 break ;
  9224.  
  9225.            case WM_PAINT:
  9226.                 PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
  9227.                 break ;
  9228.  
  9229.            default:
  9230.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9231.            }
  9232.       return 0L ;
  9233.       }
  9234.  
  9235.  
  9236.  Figure 5:
  9237.  
  9238.  BIGJOB4 Make File
  9239.  
  9240.  bigjob4.obj : bigjob4.c bigjob.h
  9241.       cl -c -FPa -G2sw -W3 -Zp bigjob4.c
  9242.  
  9243.  bigjob.res : bigjob.rc bigjob.h
  9244.       rc -r bigjob.rc
  9245.  
  9246.  bigjob4.exe : bigjob4.obj bigjob4.def bigjob.res
  9247.       link bigjob4, /align:16, /map, /nod slibc slibcp slibfa os2,
  9248.       bigjob4 rc bigjob.res bigjob4.exe
  9249.  
  9250.  BIGJOB4.DEF Module Definition File
  9251.  
  9252.  NAME           BIGJOB4
  9253.  DESCRIPTION    'BigJob Demo Program No. 4(C) Charles Petzold, 1988'
  9254.  HEAPSIZE       1024
  9255.  STACKSIZE      8192
  9256.  EXPORTS        ClientWndProc
  9257.  
  9258.  BIGJOB4.DEF ── Second Thread Approach to Lengthy Processing Job
  9259.  
  9260.  #define INCL_WIN
  9261.  #define INCL_DOS
  9262.  
  9263.  #include <os2.h>
  9264.  #include <math.h>
  9265.  #include <stdio.h>
  9266.  #include "bigjob.h"
  9267.  
  9268.  #define WM_CALC_DONE     (WM_USER + 0)
  9269.  #define WM_CALC_ABORTED  (WM_USER + 1)
  9270.  
  9271.  VOID FAR SecondThread (VOID) ;
  9272.  
  9273.  BOOL  bContinueCalc = FALSE ;
  9274.  HWND  hwndClient ;
  9275.  SHORT iCalcRep ;
  9276.  LONG  lSemTrigger ;
  9277.  TID   idThread ;
  9278.  UCHAR cThreadStack [4096] ;
  9279.  
  9280.  INT main (VOID)
  9281.       {
  9282.       static CHAR szClassName [] = "BigJob4" ;
  9283.       HMQ         hmq ;
  9284.       HWND        hwndFrame ;
  9285.       QMSG        qmsg ;
  9286.  
  9287.       hab = WinInitialize (0) ;
  9288.       hmq = WinCreateMsgQueue (hab, 0) ;
  9289.  
  9290.       WinRegisterClass (hab, szClassName, ClientWndProc,
  9291.                              CS_SIZEREDRAW, 0, NULL) ;
  9292.  
  9293.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  9294.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  9295.                                 | FS_SYSMENU    | FS_MINMAX
  9296.                                 | FS_MENU,
  9297.                      szClassName, "BigJob Demo No. 4",
  9298.                      0L, NULL, ID_RESOURCE, &hwndClient) ;
  9299.  
  9300.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9301.            WinDispatchMsg (hab, &qmsg) ;
  9302.  
  9303.       DosSuspendThread (idThread) ;
  9304.  
  9305.       WinDestroyWindow (hwndFrame) ;
  9306.       WinDestroyMsgQueue (hmq) ;
  9307.       WinTerminate (hab) ;
  9308.  
  9309.       return 0 ;
  9310.       }
  9311.  
  9312.  ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
  9313.                                ULONG mp2)
  9314.       {
  9315.       static SHORT iCurrentRep = IDM_10 ;
  9316.       static SHORT iStatus = STATUS_READY ;
  9317.       static ULONG lElapsedTime ;
  9318.  
  9319.       switch (msg)
  9320.            {
  9321.            case WM_CREATE:
  9322.  
  9323.                 DosSemSet (&lSemTrigger) ;
  9324.  
  9325.                 if (DosCreateThread (SecondThread, &idThread,
  9326.                                      cThreadStack + sizeof
  9327.                                      cThreadStack))
  9328.  
  9329.                      WinAlarm (HWND_DESKTOP, WA_ERROR) ;
  9330.  
  9331.                 break ;
  9332.  
  9333.            case WM_COMMAND:
  9334.  
  9335.                 switch (LOUSHORT (mp1))
  9336.                      {
  9337.                      case IDM_10:
  9338.                      case IDM_100:
  9339.                      case IDM_1000:
  9340.                      case IDM_10000:
  9341.                           CheckMenuItem (hwnd, iCurrentRep,
  9342.                                          FALSE) ;
  9343.                           iCurrentRep = LOUSHORT (mp1) ;
  9344.                           CheckMenuItem (hwnd, iCurrentRep,
  9345.                                          TRUE) ;
  9346.                           break ;
  9347.  
  9348.                      case IDM_START:
  9349.                           iStatus = STATUS_WORKING ;
  9350.                           WinInvalidateRect (hwnd, NULL) ;
  9351.  
  9352.                           iCalcRep = iCurrentRep ;
  9353.                           bContinueCalc = TRUE ;
  9354.                           DosSemClear (&lSemTrigger) ;
  9355.  
  9356.                           EnableMenuItem (hwnd, IDM_START,
  9357.                                           FALSE) ;
  9358.                           EnableMenuItem (hwnd, IDM_ABORT,
  9359.                                           TRUE);
  9360.                           break ;
  9361.  
  9362.                      case IDM_ABORT:
  9363.                           bContinueCalc = FALSE ;
  9364.  
  9365.                           EnableMenuItem (hwnd, IDM_ABORT,
  9366.                                           FALSE) ;
  9367.                           break ;
  9368.  
  9369.                      default:
  9370.                           break ;
  9371.                      }
  9372.                 break ;
  9373.  
  9374.            case WM_CALC_DONE:
  9375.  
  9376.                 iStatus = STATUS_DONE ;
  9377.                 lElapsedTime = mp1 ;
  9378.                 WinInvalidateRect (hwnd, NULL) ;
  9379.  
  9380.                 EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9381.                 EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9382.                 break ;
  9383.  
  9384.            case WM_CALC_ABORTED:
  9385.  
  9386.                 iStatus = STATUS_READY ;
  9387.                 WinInvalidateRect (hwnd, NULL) ;
  9388.  
  9389.                 EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9390.                 break ;
  9391.  
  9392.            case WM_PAINT:
  9393.                 PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
  9394.                 break ;
  9395.  
  9396.            default:
  9397.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9398.            }
  9399.       return 0L ;
  9400.       }
  9401.  
  9402.  VOID FAR SecondThread ()
  9403.       {
  9404.       double A ;
  9405.       int    i ;
  9406.       LONG   lTime ;
  9407.  
  9408.       while (1)
  9409.            {
  9410.            DosSemWait (&lSemTrigger, -1L) ;
  9411.  
  9412.            lTime = WinGetCurrentTime (hab) ;
  9413.  
  9414.            for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
  9415.                 {
  9416.                 if (!bContinueCalc)
  9417.                      break ;
  9418.  
  9419.                 A = Savage (A) ;
  9420.                 }
  9421.  
  9422.            lTime = WinGetCurrentTime (hab) - lTime ;
  9423.  
  9424.            DosSemSet (&lSemTrigger) ;
  9425.  
  9426.            if (bContinueCalc)
  9427.                 WinPostMsg (hwndClient, WM_CALC_DONE, lTime, 0L) ;
  9428.            else
  9429.                 WinPostMsg (hwndClient, WM_CALC_ABORTED, 0L, 0L) ;
  9430.            }
  9431.       }
  9432.  
  9433.  
  9434.  Figure 6:
  9435.  
  9436.  BIGJOB5 Make File
  9437.  
  9438.  bigjob5.obj : bigjob5.c bigjob.h
  9439.       cl -c -FPa -G2sw -W3 -Zp bigjob5.c
  9440.  
  9441.  bigjob.res : bigjob.rc bigjob.h
  9442.       rc -r bigjob.rc
  9443.  
  9444.  bigjob5.exe : bigjob5.obj bigjob5.def bigjob.res
  9445.       link bigjob5, /align:16, /map, /nod slibc slibcp slibfa os2,
  9446.       bigjob5 rc bigjob.res bigjob5.exe
  9447.  
  9448.  BIGJOB5.DEF Module Definition File
  9449.  
  9450.  NAME           BIGJOB5
  9451.  DESCRIPTION    'BigJob Demo Program No. 5(C) Charles Petzold, 1988'
  9452.  HEAPSIZE       1024
  9453.  STACKSIZE      8192
  9454.  EXPORTS        ClientWndProc
  9455.                 ObjectWndProc
  9456.  
  9457.  BIGJOB5.C - Object Window Approach to Lengthy Processing Job
  9458.  
  9459.  #define INCL_WIN
  9460.  #define INCL_DOS
  9461.  
  9462.  #include <os2.h>
  9463.  #include <math.h>
  9464.  #include <stdio.h>
  9465.  #include "bigjob.h"
  9466.  
  9467.  #define WM_OBJECT_CREATED     (WM_USER + 0)
  9468.  #define WM_START_CALC         (WM_USER + 1)
  9469.  #define WM_ABORT_CALC         (WM_USER + 2)
  9470.  #define WM_CALC_DONE          (WM_USER + 3)
  9471.  #define WM_CALC_ABORTED       (WM_USER + 4)
  9472.  #define WM_OBJECT_DESTROYED   (WM_USER + 5)
  9473.  
  9474.  VOID  FAR SecondThread (VOID) ;
  9475.  ULONG EXPENTRY ObjectWndProc (HWND, USHORT, ULONG, ULONG) ;
  9476.  
  9477.  HWND  hwndClient, hwndObject ;
  9478.  UCHAR cThreadStack [8192] ;
  9479.  
  9480.  INT main (VOID)
  9481.       {
  9482.       static CHAR szClassName [] = "BigJob5" ;
  9483.       HMQ         hmq ;
  9484.       HWND        hwndFrame ;
  9485.       QMSG        qmsg ;
  9486.       TID         idThread ;
  9487.  
  9488.       hab = WinInitialize (0) ;
  9489.       hmq = WinCreateMsgQueue (hab, 0) ;
  9490.  
  9491.       WinRegisterClass (hab, szClassName, ClientWndProc,
  9492.                              CS_SIZEREDRAW, 0, NULL) ;
  9493.  
  9494.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  9495.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  9496.                                 | FS_SYSMENU    | FS_MINMAX
  9497.                                 | FS_MENU,
  9498.                      szClassName, "BigJob Demo No. 5",
  9499.                      0L, NULL, ID_RESOURCE, &hwndClient) ;
  9500.  
  9501.       EnableMenuItem (hwndClient, IDM_START, FALSE) ;
  9502.  
  9503.       if (DosCreateThread (SecondThread, &idThread,
  9504.                            cThreadStack + sizeof cThreadStack))
  9505.  
  9506.            WinAlarm (HWND_DESKTOP, WA_ERROR) ;
  9507.  
  9508.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9509.            WinDispatchMsg (hab, &qmsg) ;
  9510.  
  9511.       WinDestroyWindow (hwndFrame) ;
  9512.       WinDestroyMsgQueue (hmq) ;
  9513.       WinTerminate (hab) ;
  9514.  
  9515.       return 0 ;
  9516.       }
  9517.  
  9518.  ULONG EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, ULONG mp1,
  9519.                                ULONG mp2)
  9520.       {
  9521.       static SHORT iCalcRep, iCurrentRep = IDM_10 ;
  9522.       static SHORT iStatus = STATUS_READY ;
  9523.       static ULONG lElapsedTime ;
  9524.  
  9525.       switch (msg)
  9526.            {
  9527.            case WM_OBJECT_CREATED:
  9528.  
  9529.                 EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9530.                 break ;
  9531.  
  9532.            case WM_COMMAND:
  9533.  
  9534.                 switch (LOUSHORT (mp1))
  9535.                      {
  9536.                      case IDM_10:
  9537.                      case IDM_100:
  9538.                      case IDM_1000:
  9539.                      case IDM_10000:
  9540.                           CheckMenuItem (hwnd, iCurrentRep, FALSE) ;
  9541.                           iCurrentRep = LOUSHORT (mp1) ;
  9542.                           CheckMenuItem (hwnd, iCurrentRep, TRUE) ;
  9543.                           break ;
  9544.  
  9545.                      case IDM_START:
  9546.                           EnableMenuItem (hwnd, IDM_START, FALSE) ;
  9547.                           EnableMenuItem (hwnd, IDM_ABORT, TRUE) ;
  9548.  
  9549.                           iStatus = STATUS_WORKING ;
  9550.                           WinInvalidateRect (hwnd, NULL) ;
  9551.  
  9552.                           iCalcRep = iCurrentRep ;
  9553.                           WinPostMsg (hwndObject, WM_START_CALC,
  9554.                                     MAKEULONG (iCalcRep, 0), 0L) ;
  9555.                           break ;
  9556.  
  9557.                      case IDM_ABORT:
  9558.                           WinPostMsg (hwndObject, WM_ABORT_CALC,
  9559.                                       0L, 0L) ;
  9560.  
  9561.                           EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9562.                           break ;
  9563.  
  9564.                      default:
  9565.                           break ;
  9566.                      }
  9567.                 break ;
  9568.  
  9569.            case WM_CALC_DONE:
  9570.  
  9571.                 iStatus = STATUS_DONE ;
  9572.                 lElapsedTime = mp1 ;
  9573.                 WinInvalidateRect (hwnd, NULL) ;
  9574.  
  9575.                 EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9576.                 EnableMenuItem (hwnd, IDM_ABORT, FALSE) ;
  9577.                 break ;
  9578.  
  9579.            case WM_CALC_ABORTED:
  9580.  
  9581.                 iStatus = STATUS_READY ;
  9582.                 WinInvalidateRect (hwnd, NULL) ;
  9583.  
  9584.                 EnableMenuItem (hwnd, IDM_START, TRUE) ;
  9585.                 break ;
  9586.  
  9587.            case WM_PAINT:
  9588.  
  9589.                 PaintWindow (hwnd, iStatus, iCalcRep, lElapsedTime) ;
  9590.                 break ;
  9591.  
  9592.            case WM_CLOSE:
  9593.  
  9594.                 if (hwndObject)
  9595.                      WinPostMsg (hwndObject, WM_QUIT, 0L, 0L) ;
  9596.                 else
  9597.                      WinPostMsg (hwnd, WM_QUIT, 0L, 0L) ;
  9598.                 break ;
  9599.  
  9600.            case WM_OBJECT_DESTROYED:
  9601.  
  9602.                 WinPostMsg (hwnd, WM_QUIT, 0L, 0L) ;
  9603.                 break ;
  9604.  
  9605.            default:
  9606.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9607.            }
  9608.       return 0L ;
  9609.       }
  9610.  
  9611.  VOID FAR SecondThread ()
  9612.       {
  9613.       static CHAR szClassName [] = "BigJob5.Object" ;
  9614.       HMQ         hmq ;
  9615.       QMSG        qmsg ;
  9616.  
  9617.       hmq = WinCreateMsgQueue (hab, 0) ;
  9618.  
  9619.       WinRegisterClass (hab, szClassName, ObjectWndProc, 0L, 0, NULL) ;
  9620.  
  9621.       hwndObject = WinCreateWindow (HWND_OBJECT, szClassName,
  9622.                      NULL, 0L, 0, 0, 0, 0, NULL, NULL, 0, NULL, NULL) ;
  9623.  
  9624.       WinPostMsg (hwndClient, WM_OBJECT_CREATED, 0L, 0L) ;
  9625.  
  9626.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9627.            WinDispatchMsg (hab, &qmsg) ;
  9628.  
  9629.       WinDestroyWindow (hwndObject) ;
  9630.       WinDestroyMsgQueue (hmq) ;
  9631.  
  9632.       WinPostMsg (hwndClient, WM_OBJECT_DESTROYED, 0L, 0L) ;
  9633.  
  9634.       DosExit (0, 0) ;
  9635.       }
  9636.  
  9637.  ULONG EXPENTRY ObjectWndProc (HWND hwnd, USHORT msg, ULONG mp1,
  9638.                                ULONG mp2)
  9639.       {
  9640.       double A ;
  9641.       SHORT  i, iCalcRep ;
  9642.       LONG   lQueueStatus, lTime ;
  9643.  
  9644.       switch (msg)
  9645.            {
  9646.            case WM_START_CALC:
  9647.  
  9648.                 iCalcRep = LOUSHORT (mp1) ;
  9649.                 lTime = WinGetCurrentTime (hab) ;
  9650.  
  9651.                 for (A = 1.0, i = 0 ; i < iCalcRep ; i++)
  9652.                      {
  9653.                      lQueueStatus = WinQueryQueueStatus (HWND_DESKTOP) ;
  9654.  
  9655.                      if (lQueueStatus & QS_POSTMSG)
  9656.                           break ;
  9657.  
  9658.                      A = Savage (A) ;
  9659.                      }
  9660.  
  9661.                 if (lQueueStatus & QS_POSTMSG)
  9662.                      break ;
  9663.  
  9664.                 lTime = WinGetCurrentTime (hab) - lTime ;
  9665.  
  9666.                 WinPostMsg (hwndClient, WM_CALC_DONE, lTime, 0L) ;
  9667.                 break ;
  9668.  
  9669.            case WM_ABORT_CALC:
  9670.  
  9671.                 WinPostMsg (hwndClient, WM_CALC_ABORTED, 0L, 0L) ;
  9672.                 break ;
  9673.  
  9674.            default:
  9675.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9676.            }
  9677.       return 0L ;
  9678.       }
  9679.  
  9680.  ████████████████████████████████████████████████████████████████████████████
  9681.  
  9682.  OS/2 LAN Manager Provides a Platform for Server-Based Network Applications
  9683.  
  9684.  Alan Kesler
  9685.  
  9686.  The introduction of personal computer──based networks has provided
  9687.  workgroups with systems that are capable of sharing expensive peripherals
  9688.  and supporting applications. This article provides a historical perspective
  9689.  of application development on PC network platforms, examines the impact of
  9690.  the OS/2 systems on PC network applications, and introduces the programmatic
  9691.  interfaces and capabilities provided by the Microsoft(R) OS/2 LAN Manager.
  9692.  
  9693.  
  9694.  Historical Perspective
  9695.  
  9696.  Applications that operate on PC networks can be categorized in one of
  9697.  three ways. Standalone applications see the network as nothing more than a
  9698.  shared printer and shared disk. They could just as easily be used on a
  9699.  standalone personal computer. Network aware applications recognize the
  9700.  existence of the network and use some of the network communication and
  9701.  data-sharing capabilities. Multiuser databases like dBASE III PLUS(TM) or
  9702.  Paradox(R) are the best examples of applications that are network aware.
  9703.  Many network aware applications may also be used in a standalone
  9704.  configuration.
  9705.  
  9706.  Finally, there are network intrinsic applications. These
  9707.  applications──electronic mail, multiuser document editing, and distributed
  9708.  database applications──don't make sense in a single personal computer or a
  9709.  standalone configuration. What sets these applications apart from the
  9710.  standalone and network aware applications is their use of distributed
  9711.  computing on the PC LAN; with standalone and network aware applications,
  9712.  all computing occurs in the network workstation. Only the program file and
  9713.  data are downloaded from the network server.
  9714.  
  9715.  This style of network-based computing has some disadvantages, such as
  9716.  underutilization of computing power sitting idle elsewhere on the
  9717.  network. Moreover, data-intensive applications will suffer performance
  9718.  problems when all computing occurs in the workstation. With back-end
  9719.  database servers, applications designed on a server and requester model
  9720.  can offer much greater performance to many more users. Finally,
  9721.  security is threatened by database applications that place large amounts
  9722.  of unnecessary data on the network, since all data must be sent to the
  9723.  network workstation in order to be processed. With distributed
  9724.  applications, user permission and data access can be verified at the
  9725.  server while a request is being processed. Only the requested information
  9726.  need be placed on the network and sent to the requesting workstation.
  9727.  
  9728.  Network intrinsic applications are set apart from both standalone and
  9729.  network aware applications through their use of distributed computing. In
  9730.  a distributed computing model the network application is partitioned
  9731.  into a workstation or front-end process and a server-based or back-end
  9732.  process. The workstation sends transactions to, and receives responses
  9733.  from, the server process. One example is a store-and-forward electronic
  9734.  mail system for a PC network. Users send electronic mail messages from
  9735.  their workstations using a message editor. The message is then sent and
  9736.  accepted by a mail server that runs in a network server on the LAN. The
  9737.  mail server stores and forwards the message along the delivery path until
  9738.  it is requested for delivery by the recipient.
  9739.  
  9740.  
  9741.  Few Distributed Apps
  9742.  
  9743.  The most frequently used applications on a network are standalone and
  9744.  network aware applications. There have been few network intrinsic or
  9745.  distributed network applications. Reasons for the lack of distributed
  9746.  network applications include the lack of a sophisticated operating
  9747.  system for the PC network platform, proprietary server environments, and
  9748.  the complexity of programming.
  9749.  
  9750.  During the 1980s, we've seen computing platforms downsized from
  9751.  mainframes and minicomputers to desktop personal computers that provide
  9752.  similar or even more raw computing power than their larger predecessors.
  9753.  Unlike the downsizing of computing platforms from the mainframe to the
  9754.  minicomputer, we have not seen a large-scale migration of crucial data
  9755.  processing applications to the desktop personal computer or PC network.
  9756.  The hardware platform has been in place with more powerful 80386-based
  9757.  workstations, but the software has, for the most part, not followed. This
  9758.  is because PC operating systems and PC LAN system software lacked the
  9759.  power and sophistication of a multitasking operating system required for
  9760.  many mission critical data processing tasks.
  9761.  
  9762.  Many believe that with IBM and Microsoft's introducing Operating System/2,
  9763.  applications once unable to operate on PC network systems will now
  9764.  migrate down to the PC network platform. IBM's Systems Application
  9765.  Architecture (SAA) is rooted in the concept of application and data
  9766.  portability across IBM's large, medium, and Personal System/2(TM) systems.
  9767.  
  9768.  
  9769.  Proprietary Environments
  9770.  
  9771.  The network system software that functions in a network file server is
  9772.  designed to provide high-performance, multiuser access to shared network
  9773.  resources such as disks and printers. Network system software vendors
  9774.  like 3Com, Novell, and IBM each implemented a proprietary means of
  9775.  making DOS a multitasking operating system, since network server software
  9776.  is by nature multitasking. The proprietary software environments, some
  9777.  of which were openly documented by the vendor, were all based on
  9778.  different application programming interfaces (APIs). An application
  9779.  developer who wanted to create a distributed network application for the
  9780.  leading network system software providers needed to implement the server
  9781.  portion of the application several times, once for each leading network
  9782.  system vendor. Nearly all application developers elected not to write to
  9783.  these proprietary interfaces for the reasons of maintenance,
  9784.  duplicate development effort, and inevitable support problems. Few
  9785.  distributed applications were developed to these proprietary interfaces,
  9786.  and most distributed applications were developed directly by the network
  9787.  system software providers who knew the proprietary environment and`were
  9788.  capable of supporting the software interfaces.
  9789.  
  9790.  The introduction of Operating System/2 changes the development
  9791.  environment dramatically. Now, leading network system software vendors
  9792.  will support OS/2 as an industry-standard multitasking kernel within the
  9793.  network server. Developers of network distributed applications have
  9794.  the advantage of an industry-standard operating system that has more
  9795.  than 250 application programming interfaces. Also, those who create
  9796.  applications that use OS/2 as the operating system for the workstation
  9797.  portion of the application have the added advantage of writing to the
  9798.  same APIs for both the workstation and the server software.
  9799.  
  9800.  
  9801.  APIs
  9802.  
  9803.  Historically, developers of network applications have had an opportunity
  9804.  to choose from several APIs for data-sharing and interprocess
  9805.  communication services. These include APIs supported by DOS 3.x and the
  9806.  Microsoft redirector, network BIOS or NetBIOS APIs, and APIs directly in
  9807.  the transport layer of network protocols.
  9808.  
  9809.  DOS 3.x and redirector APIs provide basic file-sharing and record-locking
  9810.  capabilities. Network aware applications such as dBASE III PLUS are
  9811.  written to these APIs. This class of application involves intensive data
  9812.  sharing.
  9813.  
  9814.  Transport-specific APIs enable a developer to create communications-
  9815.  oriented applications. Applications such as electronic mail, local and
  9816.  remote network bridging, and message routing have the advantage of using
  9817.  the specific capabilities of a given network transport. Unfortunately,
  9818.  applications developed to a specific interface are not portable if
  9819.  another network transport layer is used. For example, an application
  9820.  that interfaces directly to TCP/IP for its internetworking support cannot
  9821.  function on an IBM or ISO protocol without first being modified to
  9822.  interface with the specific functions of those respective network
  9823.  transport protocols.
  9824.  
  9825.  NetBIOS, a communication-intensive protocol introduced with IBM's PC Net
  9826.  in 1985, supports session-level interaction between workstations and
  9827.  servers. As the name implies, NetBIOS is a low-level protocol that
  9828.  requires the application developer to write directly to a network BIOS.
  9829.  Perhaps the greatest advantage of the NetBIOS interface is that it is
  9830.  supported or emulated by all leading network system software vendors. An
  9831.  application developer who writes to the NetBIOS API can be reasonably sure
  9832.  that the application will run on many network systems and therefore
  9833.  garner a large market share. However, NetBIOS requires the application
  9834.  developer to understand a detailed communication protocol at very low
  9835.  levels in the system, therefore making NetBIOS applications difficult to
  9836.  develop. Also, error conditions that result from NetBIOS transactions
  9837.  (send, receive, etc.) need special programming over and above that
  9838.  required by the underlying operating system. Applications developed to
  9839.  the NetBIOS protocol include electronic mail and transaction-oriented
  9840.  database applications.
  9841.  
  9842.  
  9843.  The OS/2 LAN Manager
  9844.  
  9845.  The Microsoft OS/2 LAN Manager is a network system software product that
  9846.  provides file, print, resource, security, and network management services
  9847.  to a work group using a local area network. A joint development project
  9848.  of Microsoft and 3Com, the OS/2 LAN Manager is based on an OS/2 network
  9849.  kernel, supports both DOS and OS/2 network workstations, has sophisticated
  9850.  network management features, and provides an open system with complete
  9851.  application programmer interfaces, including programming documentation and
  9852.  development tools.
  9853.  
  9854.  The Microsoft OS/2 LAN Manager includes both workstation and server
  9855.  software. The workstation software includes user interface and requester
  9856.  functions for DOS and OS/2 network workstations. The server operates on
  9857.  top of the OS/2 kernel. Thus, as an application, the OS/2 LAN Manager is
  9858.  hosted by OS/2 and is an OS/2 application in the network server. Because
  9859.  it functions as an OS/2 application in the server, it can take advantage
  9860.  of the features of OS/2, including the ability to use the 16Mb of
  9861.  protected-mode memory provided by OS/2. The protected-mode memory allows
  9862.  OS/2 LAN Manager to coexist with any other application written to OS/2
  9863.  APIs. Applications written to OS/2 can operate side-by-side in the same
  9864.  computer that is acting as a network server for a PC LAN. This is perhaps
  9865.  the most important benefit gained from the use of the OS/2 kernel.
  9866.  
  9867.  The OS/2 LAN Manager is based on OS/2, unlike network system software that
  9868.  is based on proprietary implementations of multitasking DOS. Any
  9869.  application developer will be able to create an application that uses
  9870.  any of the more than 250 APIs within OS/2 and the application can run
  9871.  side-by-side with the OS/2 LAN Manager. This frees developers from writing
  9872.  to proprietary server interfaces. It is important to note that IBM has
  9873.  announced a network system software product called OS/2 LAN Server, which
  9874.  also is hosted by OS/2.
  9875.  
  9876.  
  9877.  Protocol-Independent
  9878.  
  9879.  Applications written to take advantage of the security, network
  9880.  management, or any other LAN Manager feature are portable to many
  9881.  different network environments. This is because the OS/2 LAN Manager
  9882.  itself is designed to be a transport protocol──independent system. The
  9883.  diagram in Figure 1 describes the framework of multiple network transport
  9884.  layers supported beneath OS/2 LAN Manager. For reference, the Microsoft
  9885.  OS/2 LAN Manager architecture is mapped against the Open Systems
  9886.  Interconnection (OSI) model.
  9887.  
  9888.  Network system software like 3Com's 3+Open(TM) product line that incorporates
  9889.  the Microsoft OS/2 LAN Manager will be supported on multiple network
  9890.  transport layers. Network applications written to take advantage of
  9891.  OS/2 LAN Manager APIs will have an opportunity for a greater share of the
  9892.  market because the Microsoft OS/2 LAN Manager will support specific needs
  9893.  of more environments by giving the customer the choice of network
  9894.  transport. As shown in Figure 1, the OS/2 LAN Manager has several
  9895.  protocol-independent interfaces including the NetBIOS and hardware
  9896.  adapter driver (Media Access Control) levels. Future developments will
  9897.  yield a network transport programming interface and an IBM(R) LLC (Logical
  9898.  Link Control) 802.2 interface.
  9899.  
  9900.  
  9901.  Backward-Compatible
  9902.  
  9903.  The Microsoft OS/2 LAN Manager supports equivalent calls to today's
  9904.  application programmer interfaces (NetBIOS, DOS redirector calls) in both
  9905.  an OS/2 and DOS environment. Today's DOS and NetBIOS APIs are supported
  9906.  and are compatible with the installed base of network applications
  9907.  written to DOS and NetBIOS. These include both normal DOS calls and
  9908.  Interrupt 5CH calls from the OS/2 compatibility mode.
  9909.  
  9910.  
  9911.  An Open System
  9912.  
  9913.  The OS/2 LAN Manager is an open system. Application programmer interfaces
  9914.  are provided for all important system functions. The system is hosted by
  9915.  OS/2, making it fully compatible with the more than 250 APIs provided by
  9916.  the kernel, in addition to the APIs shown in Figure 2.
  9917.  
  9918.  
  9919.  IPC
  9920.  
  9921.  Highly integrated with the OS/2 kernel over which it operates, the
  9922.  Microsoft OS/2 LAN Manager extends the capabilities of OS/2 "pipes" or
  9923.  interprocess communication across the network, allowing an application
  9924.  to use remote procedure calls to processes running in the same computer
  9925.  or a different one elsewhere on the network. This extension or
  9926.  redirection of OS/2 interprocess communication across the network is
  9927.  often referred to as "named pipes" or redirected pipes. It is this ability
  9928.  to redirect interprocess communication across the network that is the
  9929.  foundation of distributed and network intrinsic applications (see
  9930.  Figure 3).
  9931.  
  9932.  Of course, named pipes are not required to build an application that
  9933.  uses PC LAN interprocess communication and remote procedure calls. What
  9934.  makes the OS/2 LAN Manager implementation of named pipes different is
  9935.  the ease of implementation for the developer when compared with similar
  9936.  implementations based on NetBIOS or other program-to-program communications.
  9937.  Where NetBIOS and other program-to-program protocols often require specific,
  9938.  in-depth knowledge of networking issues, named pipes provide a high-level
  9939.  interface that offers developers of DOS-based applications an opportunity to
  9940.  quickly and easily create network intrinsic applications.
  9941.  
  9942.  
  9943.  Remote Procedure Calls
  9944.  
  9945.  Before providing an in-depth comparison of the use of named pipes versus
  9946.  NetBIOS for remote procedure calls on a PC network, it helps to review the
  9947.  types of transactions required to perform this task. The example below is
  9948.  generalized to include the procedures necessary for a secure,
  9949.  transaction-oriented, comprehensive commercial application. Included in
  9950.  the application are user authentication, error logging, access control, and
  9951.  access logging.
  9952.  
  9953.  In general, the requesting workstation and responding server will have to
  9954.  perform the following tasks:
  9955.  
  9956.    ■  user asks for a session
  9957.  
  9958.    ■  user sends username
  9959.  
  9960.    ■  server sends a challenge that is used by the workstation to provide
  9961.       the correct password validation back to the server
  9962.  
  9963.    ■  user sends password
  9964.  
  9965.    ■  server validates password, username
  9966.  
  9967.    ■  user sends remote procedure call
  9968.  
  9969.    ■  server checks for access control (can the user perform the remote
  9970.       procedure call)
  9971.  
  9972.    ■  server performs proper error response and logging if
  9973.       access/permission denied
  9974.  
  9975.    ■  server performs remote procedure call
  9976.  
  9977.    ■  server logs access to server process used by remote procedure call
  9978.  
  9979.    ■  server closes session
  9980.  
  9981.    ■  server gets next request
  9982.  
  9983.  
  9984.  Named Pipes vs. NetBIOS
  9985.  
  9986.  Named pipes are transaction-oriented and provide a much higher-level
  9987.  interface to the application developer. The calling convention of named
  9988.  pipes is much less complicated than that required with a NetBIOS solution.
  9989.  A single named pipe function call equates to many NetBIOS calls. To
  9990.  demonstrate the relative simplicity of the named pipe API in relation to
  9991.  NetBIOS APIs, a side-by-side comparison is provided in Figure 4.
  9992.  
  9993.  It is important to note that the named pipe example is presented in C
  9994.  pseudocode and more closely approximates real C language code. The NetBIOS
  9995.  example is presented with a high-level procedural description of steps
  9996.  that would need significant embellishment to evolve to the detail of the C
  9997.  language pseudocode. This was done because the NetBIOS example would be
  9998.  too lengthy if provided in detailed C pseudocode. Even with a pseudocode
  9999.  example, the differences in complexity of the calling convention between
  10000.  named pipes and NetBIOS is obvious.
  10001.  
  10002.  A remote procedure call requires two basic pieces: the workstation portion
  10003.  that initiates the call and a server portion that responds to and acts
  10004.  on the call request.
  10005.  
  10006.  The function call necessary to request a remote procedure call from an
  10007.  OS/2 LAN Manager workstation to an OS/2 LAN Manager server is provided in
  10008.  Figure 4. For clarity, some of the arguments (like buffer lengths, for
  10009.  example) have been excluded from the C pseudocode example.
  10010.  
  10011.  You will notice that the DosCallNmPipe function takes the name of a
  10012.  server, the name of a pipe, application name and request, and receive
  10013.  buffer as arguments. The complete remote procedure call request is
  10014.  performed with a single function call. All user validation, auditing,
  10015.  error checking, and reporting is performed automatically by the OS/2
  10016.  LAN Manager server, since a named pipe can be shared by an OS/2 LAN
  10017.  Manager server. The OS/2 LAN Manager security system, network management,
  10018.  audit trail, resource accounting, and user validation are a side benefit
  10019.  of using named pipes.
  10020.  
  10021.  To provide a similar capability using a NetBIOS API on an OS/2 LAN
  10022.  Manager system would require workstation code with the functional
  10023.  characteristics shown in Figure 4.
  10024.  
  10025.  Notice that with the NetBIOS example, not only is the remote procedure
  10026.  call more complex, but special-purpose code must be developed to validate
  10027.  user access, report on resource usage, and handle special case error
  10028.  conditions. Named pipes are treated as any other shared server resource,
  10029.  and thus special-purpose code is not necessary as the error conditions,
  10030.  validation, and reporting are handled automatically by the Microsoft
  10031.  OS/2 LAN Manager. It is also important to note that the NetBIOS example
  10032.  does not include special code to handle error conditions that would
  10033.  require clean-up operations.
  10034.  
  10035.  Named pipes can be either stateless, like DosCallNmPipe, or allow remote
  10036.  procedure calls, like DosTransactNmPipe. Use of the DosCallNmPipe
  10037.  procedure call provides for stateless remote procedure calls (sessions
  10038.  only maintained during a transaction) and thus are less prone to error and
  10039.  don't require special clean-up. Use of DosTransactNmPipe allows a series
  10040.  of RPCs on the same session with the network server.
  10041.  
  10042.  In addition to error conditions that result from session management, hard
  10043.  errors, such as Abort, Retry, Ignore, Fail, occur. Since named pipes are
  10044.  an extension of OS/2 pipes, built-in OS/2 error recovery is
  10045.  automatically used should a hard error occur. With NetBIOS, hard errors
  10046.  must be anticipated and dealt with within the application code itself.
  10047.  
  10048.  
  10049.  Remote Procedure: Server Portion
  10050.  
  10051.  Remote procedure calls that are initiated using named pipes from the
  10052.  workstation must be supported by remote procedure management code on the
  10053.  network server. The pseudocode in Figure 5 shows the APIs required by
  10054.  the network server portion of the distributed application, as well as
  10055.  the pseudocode necessary to provide similar capabilities if NetBIOS is
  10056.  used rather than named pipes.
  10057.  
  10058.  Note that in the NetBIOS example, all password and user verification,
  10059.  reporting, auditing, and resource management must be supported directly by
  10060.  the application code running in the network server.
  10061.  
  10062.  The system-level capabilities - portability, stateless operation,
  10063.  multiple RPC calls, auditing, security, administration, error handling,
  10064.  and higher performance - are built into the Microsoft OS/2 LAN Manager and
  10065.  are therefore automatically available to any application that uses named
  10066.  pipes for interprocess communication across the network.
  10067.  
  10068.  
  10069.  Portability
  10070.  
  10071.  OS/2 LAN Manager is hosted by the OS/2 kernel; therefore, applications
  10072.  written to OS/2 APIs can be ported to perform remote procedure calls and
  10073.  function in a distributed configuration on the PC LAN with only minor
  10074.  modifications to the application code. This is not true if the application
  10075.  was ported from OS/2 to use NetBIOS as an interprocess communication API.
  10076.  It is also important to note that applications written to OS/2 can run
  10077.  without modification in an OS/2 LAN Manager network server. Of course,
  10078.  this is not the case with all network system software. Many vendors who
  10079.  provide PC network systems for business work groups don't provide an
  10080.  effective OS/2 server environment for OS/2 applications installed in the
  10081.  server.
  10082.  
  10083.  
  10084.  Stateless Operation
  10085.  
  10086.  A stateless distributed application maintains a session with the server
  10087.  only during the length of a single procedure call. Each procedure call to
  10088.  the server from the workstation creates a session, performs the remote
  10089.  procedure call, and closes the session. With named pipes this is
  10090.  performed using DosCallNmPipe. To perform the remote procedure call
  10091.  using NetBIOS, the NetBIOS application must perform a call, send,
  10092.  receive, and hang up. The NetBIOS example requires the session to live
  10093.  longer than a simple request/receive transaction. This is particularly
  10094.  important with error management. The longer a session is open, the
  10095.  more likely an error may occur. If you don't have a stateless design,
  10096.  special case code has to be provided to perform a clean-up from
  10097.  potential errors.
  10098.  
  10099.  
  10100.  Multiple RPC Calls
  10101.  
  10102.  Named pipes also include procedure calls that are useful when the
  10103.  application transacts multiple remote procedure calls on the same session
  10104.  to the server. For some applications, this is the desired means for remote
  10105.  procedure calling. Applications that perform normal DOS reads, DOS
  10106.  writes, or DosTransactNmPipe calls to a named pipe are operating in such a
  10107.  multicall mode, which may offer performance advantages because the
  10108.  session is maintained across multiple transactions and doesn't have to be
  10109.  reestablished for every transaction between the requester and the
  10110.  server.
  10111.  
  10112.  
  10113.  Auditing
  10114.  
  10115.  Many distributed applications require sophisticated audit trail facilities
  10116.  that track who performed the remote procedure call, from which
  10117.  workstation, at what time, and how long the remote procedure call took.
  10118.  Using a NetBIOS-based distributed application, all of the functions
  10119.  required for auditing would have to be specially developed as part of the
  10120.  distributed application. With the Microsoft OS/2 LAN Manager, the
  10121.  network administrator can flag one or more specific named pipes that exist
  10122.  on the server and mark them to be audited; this causes a record to be
  10123.  entered in the OS/2 LAN Manager audit trail each time a named pipe is
  10124.  opened or closed. The record generated in the audit trail can be used as
  10125.  the foundation of a bill-back system. It includes the date and time of
  10126.  access, who accessed the named pipe, the type of access (open/close), and
  10127.  the duration of the access.
  10128.  
  10129.  
  10130.  Security
  10131.  
  10132.  The Microsoft OS/2 LAN Manager incorporates a sophisticated user
  10133.  security system that includes encrypted user log-on validation, access
  10134.  control list, error reporting, and real-time event notification of
  10135.  attempted security breaches should access to a shared resource be
  10136.  attempted by an unauthorized user. All of these capabilities are
  10137.  available to a distributed application that uses named pipes as the
  10138.  means for interprocess communication across the network. Access to named
  10139.  pipes is controlled by the server's standard user logon, password
  10140.  encryption, group membership, and access permission mechanism. The
  10141.  capabilities are provided without requiring any additional programming by
  10142.  the application developer.
  10143.  
  10144.  Treated by the server as a shared resource, the named pipe can be placed
  10145.  in the server's resource list and given access permission using the OS/2
  10146.  LAN Manager administrative software. Once logged as a shared server
  10147.  resource, the OS/2 LAN Manager will report on the status of the resource
  10148.  just as it would any other shared resource on the network. Attempts to use
  10149.  the named pipe resource will be logged in the system audit trail. Should
  10150.  excessive unauthorized access to the resource be attempted, a real-time
  10151.  message will be sent to advise a predetermined network administrator of
  10152.  the attempted security breach. Finally, network errors resulting from the
  10153.  use of the named pipe will be automatically logged in the system's error
  10154.  file and can be reviewed by the network administrator.
  10155.  
  10156.  
  10157.  Administration
  10158.  
  10159.  The OS/2 LAN Manager has a window-oriented administrative user
  10160.  interface that supports real-time administration of shared network
  10161.  resources. If distributed applications are designed to use named pipes as
  10162.  the means for interprocess communication, the LAN Manager administrative
  10163.  interface can be used to view and manage access to the redirect pipes
  10164.  without any development specifically embedded in the distributed
  10165.  application. OS/2 LAN Manager provides management of named pipes,
  10166.  including the ability to view real-time user access to the named pipe.
  10167.  Data includes local machine name, user name, resource name, total session
  10168.  time, and session idle time.
  10169.  
  10170.  In addition, OS/2 LAN Manager provides the ability to force a user to
  10171.  disconnect from a named pipe and automatically disconnect workstation and
  10172.  server sessions based on user activity and an administrator defined
  10173.  timeout parameters. If a named pipe is open between a workstation and
  10174.  server (there is an open handle), the session will not timeout. A built-in
  10175.  pop-up message system can be used directly by the distributed application to
  10176.  inform users with a real-time message. The message system can also be used
  10177.  by the network administrator to send messages to individual users.
  10178.  
  10179.  
  10180.  Error Handling
  10181.  
  10182.  Pipe management code is part of the operating system kernel within OS/2.
  10183.  It adheres fully to the operating system's method of error reporting,
  10184.  including use of standard OS/2 error codes and issuance of hard error pop-
  10185.  ups where appropriate with abort/retry/ignore/fail error handling. Because
  10186.  NetBIOS session capability is not a supported interprocess communication
  10187.  within the operating system, distributed applications written to NetBIOS
  10188.  must include a significant amount of specific error-handling code.
  10189.  
  10190.  Named pipes also provide advantages for the server portion of the remote
  10191.  procedure code. If a named pipe fails during processing, the operating
  10192.  system performs all the cleanup. But if any NetBIOS send fails, the
  10193.  application will have to cancel any asynchronous receive requests that
  10194.  have been posted and hang up the session. Each of these clean-up tasks
  10195.  could generate an error as well, from which the application must be
  10196.  programmed to recover.
  10197.  
  10198.  
  10199.  Performance
  10200.  
  10201.  The Microsoft OS/2 LAN Manager named pipe API supports function calls
  10202.  such as DosTransactNmPipe and DosCallNmPipe that do entire transactions in
  10203.  a single system call. By contrast, multiple NetBIOS calls would be
  10204.  necessary to perform the same function as the single named pipe call.
  10205.  Because named pipes call the operating system less often, distributed
  10206.  applications based on named pipes for remote procedure calls and
  10207.  interprocess communication are likely to yield high-performance results.
  10208.  
  10209.  
  10210.  Tradeoffs
  10211.  
  10212.  Many of the advantages achieved by the developer who uses named pipes for
  10213.  PC network interprocess communication have been outlined. There are,
  10214.  however, costs to the developer who uses OS/2 LAN Manager named pipes,
  10215.  which deserve serious consideration from any developer who is planning
  10216.  to create a distributed network application.
  10217.  
  10218.  Currently, named pipes have been announced as a fully supported API on
  10219.  systems that incorporate the Microsoft OS/2 LAN Manager. 3Com's 3+Open
  10220.  product line is an example of one such system. Although it is reasonable
  10221.  to assume that other vendors who provide network system software product
  10222.  lines will incorporate the Microsoft OS/2 LAN Manager product, and that
  10223.  these systems will also support named pipes, there are many more installed
  10224.  network systems that support NetBIOS and will continue to do so. The
  10225.  developer of a network application will, at least initially, choose
  10226.  between capability and installed base market share. For many developers,
  10227.  the advantages of named pipes and the market position of companies who
  10228.  have announced Microsoft OS/2 LAN Manager-based systems is
  10229.  encouragement enough to forgo NetBIOS and develop to the named pipe
  10230.  interface. Even if a developer of network applications already knows and
  10231.  understands the NetBIOS API, using named pipes will require additional
  10232.  education.
  10233.  
  10234.  Microsoft has taken direct steps toward reducing the need for further
  10235.  education with its Microsoft University classes. These classes deal with
  10236.  the fundamentals of developing OS/2 applications as well as applications
  10237.  development for the Microsoft OS/2 LAN Manager. Microsoft is also
  10238.  sponsoring several OS/2 LAN Manager developers' conferences this year.
  10239.  The high-level interface and simplicity of named pipes, the classes at
  10240.  Microsoft University, and the developers' conference will benefit
  10241.  developers of any network application.
  10242.  
  10243.  
  10244.  Application Platform
  10245.  
  10246.  Although the success of network systems incorporating the OS/2 LAN Manager
  10247.  won't be known until they are introduced about mid-year 1988, the Microsoft
  10248.  OS/2 LAN Manager provides a rich platform for application development.
  10249.  
  10250.  The strong points of the Microsoft OS/2 LAN Manager currently available to
  10251.  developers of network applications and end customers include:
  10252.  
  10253.    ■  use of an industry-standard multitasking operating system, OS/2
  10254.  
  10255.    ■  high performance and reliability
  10256.  
  10257.    ■  open system architecture
  10258.  
  10259.    ■  support for OS/2 APIs as well as NetBIOS and DOS redirector calls
  10260.  
  10261.    ■  support for OS/2 pipes and extension of them across the network using
  10262.       named OS/2 pipes
  10263.  
  10264.    ■  rich system-level function that lets applications benefit from the
  10265.       administrative functions already built into the system
  10266.  
  10267.  
  10268.  Trends
  10269.  
  10270.  The advent of IBM OS/2 and the use of industry standard multitasking
  10271.  operating systems as the kernel to network system software provides an
  10272.  opportunity for the development of applications that reside in the network
  10273.  server and provide distributed computing power to the PC network. Network
  10274.  applications that once had to be developed to use proprietary, vendor-
  10275.  specific operating system interfaces will now be developed to industry-
  10276.  standard OS/2 operating system interfaces. In addition, Microsoft has
  10277.  simplified network interprocess communication with the OS/2 named pipes
  10278.  capability. OS/2 LAN Manager APIs can significantly reduce the overhead of
  10279.  developing a network intrinsic application. People in the industry believe
  10280.  that the OS/2 network servers and these new APIs will result in many new
  10281.  applications, such as database servers, mail servers, name/directory
  10282.  servers, document library services, accounting (using a distributed database
  10283.  server as the back end), and communication gateways.
  10284.  
  10285.  The industry direction by Microsoft, IBM, and 3Com of providing and
  10286.  marketing open system server platforms based on Operating System/2 as an
  10287.  industry-standard operating system kernel will benefit both the developer
  10288.  and end customer of PC network systems with more powerful and functional
  10289.  application software.
  10290.  
  10291.  
  10292.  Figure 1:  The Microsoft OS/2 LAN Manager has a protocol-independent design.
  10293.  
  10294.  ┌─────────────────────────────────────────────────────────────────────────┐
  10295.  │   ╔═════════════╗      ╔═══════════════════════════════════╗            │█
  10296.  │   ║ Application ║      ║ Microsoft OS/2 LAN Manager Server ║            │█
  10297.  │   ╟─────────────╢      ║     and Workstation Software      ║            │█
  10298.  │   ║ Presentation║      ╚═══════════════════════════════════╝            │█
  10299.  │   ╚═════════════╝                                                       │█
  10300.  │                                                                         │█
  10301.  │   ╔═════════════╗      ╔════════╗   ╔════════╗   ╔════════╗-NetBIOS     │█
  10302.  │   ║   Session   ║      ║  IBM   ║   ║        ║   ║        ║             │█
  10303.  │   ╟─────────────╢      ║NetBIOS/║   ║ TCP/IP ║   ║  ISO   ║             │█
  10304.  │   ║  Transport  ║      ║ Token  ║   ║        ║   ║        ║             │█
  10305.  │   ╟─────────────╢      ║  Ring  ║   ║        ║   ║        ║             │█
  10306.  │   ║   Network   ║      ╚════════╝   ╚════════╝   ╚════════╝             │█
  10307.  │   ╚═════════════╝                                                       │█
  10308.  │                                                                         │█
  10309.  │   ╔═════════════╗      ╔═══════════════════════════════════╗-802.2 LLC  │█
  10310.  │   ║  Data Link  ║      ║       Adapter Interface and       ║            │█
  10311.  │   ╟─────────────╢      ║          Protocol Manager         ║            │█
  10312.  │   ║   Physical  ║      ╚═══════════════════════════════════╝-Media      │█
  10313.  │   ╚═════════════╝                                            Access     │█
  10314.  │                                                              Control    │█
  10315.  └─────────────────────────────────────────────────────────────────────────┘█
  10316.    ██████████████████████████████████████████████████████████████████████████
  10317.  
  10318.  
  10319.  Figure 2:  System functions supported by an application programming
  10320.             interface include, but are not limited to, those shown here.
  10321.  
  10322.  Network Interface
  10323.  Media Access Control (network adapter
  10324.  driver interface)
  10325.  Protocol manager (to support multiple,
  10326.   simultaneous network protocols)
  10327.  
  10328.  Network Management/Administration
  10329.  Audit trail
  10330.  Messaging
  10331.  Alerter
  10332.  Error log
  10333.  Network error reporting
  10334.  Network statistics
  10335.  
  10336.  Input/Output Devices and Pipes
  10337.  Named pipes (redirected pipes)
  10338.  Spoolers
  10339.  Character devices
  10340.  NetBIOS
  10341.  Mail slots
  10342.  
  10343.  Network Access
  10344.  Use/un-use network resources
  10345.  Access control list security
  10346.  Server/workstation configuration
  10347.  Resource sharing
  10348.  Network console
  10349.  Net Run (OS/2 compute server)
  10350.  Logon scripts
  10351.  User profiles
  10352.  
  10353.  
  10354.  Figure 3:  OS/2 pipes are used to redirect interprocess communication within
  10355.             a single computer. Named pipes redirect interprocess communication
  10356.             and make it possible for applications to transparently use
  10357.             computing resources in other workstations or servers on the
  10358.             network.
  10359.  
  10360.                                               User
  10361.    ╔══════════════╗                  ┌──────Interface──────┐
  10362.    ║ ░░░░░░░░░░░  ║█                 │                     │
  10363.    ║ ░░░░░░░░░░░░ ║█             OS/2 Pipe             OS/2 Pipe
  10364.    ║ ░░░░░░░░░░░░ ║█                 │                     │
  10365.    ║ ░░░░░░░░░░ ║█                                      
  10366.  ╔═╝──────────────╚═╗            Database ──OS/2 Pipe── Report
  10367.  ║ ----  ║█           Manager                  Writer
  10368.  ╚══════════════════╝█  ─────────────────────────────────────────────────────
  10369.    ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                  ╔══════╗
  10370.      Standalone PC                      ║│     ║█ Server
  10371.                                         ║│▄▄▄▄▄║█            ╔═══════════╗
  10372.                                         ║┌───┬┐║█            ║ ░░░░░░░░  ║█
  10373.                                         ║│   ││║█            ║ ░░░░░░░░░ ║█
  10374.    ┌──────────Name         Database     ║│   ││║█            ║ ░░░░░░░░░ ║█
  10375.  User     (Redirected)────Manager      ║│   ││║█PC Network║ ░░░░░░░ ║█
  10376.  Interface  OS/2 Pipe                  ║└───┴┘║█          ╔═╝───────────╚═╗
  10377.    │                      OS/2 Pipe     ║||||||║█          ║ ----  ║█
  10378.    └──────────Name      ┌─            ║||||||║█          ╚═══════════════╝█
  10379.           (Redirected)──┘  Report       ╚══════╝█            ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  10380.             OS/2 Pipe      Writer         ▀▀▀▀▀▀▀              Workstation
  10381.  
  10382.  
  10383.  Figure 4:  A pseudocode comparison of a LAN manager API call (shown on top)
  10384.             and the equivalent NetBIOS calls (shown below).
  10385.  
  10386.  LAN Manager API Call
  10387.  
  10388.  DosCallNmPipe("\\servername\pipe\appname", RpcRequBuf, RpcRespBuf)
  10389.  
  10390.  NetBIOS API Call
  10391.  
  10392.  Begin Procedure
  10393.    Get username from the user
  10394.    Get password from the user
  10395.    Establish session with server
  10396.    Send username to server
  10397.    Receive server challenge (used for password encryption)
  10398.    Do the encryption challenge using server challenge and user password
  10399.      Result of logic is challenge response
  10400.    Send challenge response with remote procedure request to server
  10401.    Receive remote procedure call response
  10402.    Close session with server
  10403.  End Procedure
  10404.  
  10405.  
  10406.  Figure 5:  The LAN Manager itself handles a great deal of the administrative
  10407.             details required through NetBIOS.
  10408.  
  10409.  LAN Manager API Call
  10410.  
  10411.  DosMakeNmPipe(\"pipe\appname", handle)
  10412.  loop /* for each remote procedure call */
  10413.    DosConnectNmPipe(handle)
  10414.    DosRead(handle, RpcReqBuf)
  10415.    Do the remote procedure call
  10416.    DosWrite(handle, RpcRespBuf)
  10417.    DosDisconnectNmPipe(handle)
  10418.  endloop
  10419.  
  10420.  NetBIOS API Call
  10421.  
  10422.  Begin_Loop
  10423.    Listen for user session
  10424.    Establish user session
  10425.    Receive username
  10426.    Do encryption logic to generate challenge
  10427.    Send challenge
  10428.    Receive challenge response and remote procedure call request
  10429.    Do encryption logic to validate user challenge response
  10430.      Open and match username in application specific access control file
  10431.      If access control supports group memberships, then verify membership
  10432.    If validation fails,
  10433.      Generate an audit trail record that access was denied
  10434.      Send an access denied response
  10435.      Close session
  10436.      GotoBegin_Loop
  10437.    Do the remote procedure call
  10438.    Send the remote procedure call response
  10439.    Close the session
  10440.    Calculate time duration of remote procedure call handling and
  10441.         generate audit trail record of service provided for this user
  10442.  End_Loop
  10443.  
  10444.  ████████████████████████████████████████████████████████████████████████████
  10445.  
  10446.  Writing OS/2 Bimodal Device Drivers: An Examination of the DevHIp API
  10447.  
  10448.  Ray Duncan
  10449.  
  10450.  Device drivers are the modules of an operating system that issue commands
  10451.  directly to peripheral devices and cause data to be transferred between
  10452.  those devices and RAM. Drivers are thus the bottom, most hardware-
  10453.  dependent layer of the system software. They shield the operating system
  10454.  kernel from dealing with hardware I/O port addresses and the peculiarities
  10455.  and characteristics, such as number of tracks or sectors per track, of a
  10456.  particular peripheral device, just as the kernel in turn shields
  10457.  applications programs from the details of file and memory management.
  10458.  
  10459.  By convention, Microsoft(R) OS/2 device drivers reside in individual disk
  10460.  files with the extension .SYS. The essential OS/2 drivers for the keyboard,
  10461.  display, and disk device are selected on the basis of the hardware
  10462.  environment and automatically linked to the operating system during booting.
  10463.  Other optional device drivers can be loaded by DEVICE= statements in the
  10464.  CONFIG.SYS file on the boot disk and are referred to as installable drivers.
  10465.  However, all OS/2 systems drivers have the same physical and logical
  10466.  structure and an identical relationship to the OS/2 kernel.
  10467.  
  10468.  OS/2 device drivers are in many ways similar to MS-DOS(R) device drivers,
  10469.  but they also have unique capabilities and requirements, including:
  10470.  
  10471.    ■  fully interrupt-driven operation
  10472.  
  10473.    ■  bimodal (real mode and protected mode) operation
  10474.  
  10475.    ■  support for multitasking and overlapped I/O
  10476.  
  10477.    ■  the ability to deinstall after the driver is fully initialized
  10478.  
  10479.    ■  support for device monitors (applications that can register with
  10480.       the driver to filter its data stream)
  10481.  
  10482.    ■  support for ROM BIOS service calls by real-mode applications
  10483.  
  10484.  The experienced MS-DOS programmer must be particularly wary of
  10485.  terminology that sounds familiar even though the associated driver function
  10486.  is very different. A full-fledged OS/2 driver for any given device is at
  10487.  least an order of magnitude more complex than its MS-DOS equivalent.
  10488.  
  10489.  
  10490.  Device Driver Types
  10491.  
  10492.  OS/2 device drivers are categorized into two groups: character device
  10493.  drivers and block device drivers. Whether a given driver is a character
  10494.  device or block device driver determines how the associated device is
  10495.  viewed by OS/2 and what functions the driver itself must support.
  10496.  
  10497.  Character device drivers control peripheral devices that perform input
  10498.  and output a character (or byte) at a time, such as a terminal or a
  10499.  printer. A single character device driver ordinarily supports a single
  10500.  hardware unit, although a given file can contain more than one driver.
  10501.  Each character device has a one- to eight-character logical name, and can
  10502.  be opened by using this name for input or output by an application
  10503.  program as though it were a file. The logical name is strictly a means of
  10504.  identification of the driver to OS/2 and has no physical equivalent on
  10505.  the device.
  10506.  
  10507.  Block device drivers control peripheral devices that transfer data in
  10508.  chunks rather than a byte at a time. Block devices are usually randomly
  10509.  addressable, such as floppy disk or hard disk drives, but they can also be
  10510.  sequential in nature, such as a magnetic tape drive. A block driver can
  10511.  support more than one physical unit and can also map two or more logical
  10512.  units onto a single physical unit, such as a partitioned hard disk.
  10513.  
  10514.  OS/2 assigns single-letter drive identifiers (A:, B:, and so on), instead
  10515.  of logical names, to block devices. The first letter assigned to a given
  10516.  block device driver is determined solely by that driver's position in the
  10517.  chain of all drivers, that is, by the number of units supported by the
  10518.  block drivers that have been loaded before it. The total number of
  10519.  letters assigned to the driver is determined by the number of logical
  10520.  units that the driver supports.
  10521.  
  10522.  
  10523.  Device Driver Structure
  10524.  
  10525.  OS/2 device drivers are written as small-model Macro Assembler programs
  10526.  with one code and one data segment and are linked as a segmented or "new"
  10527.  EXE file. Figure 1 is a diagram of the physical structure of a device
  10528.  driver file; the data segment always lies in the first part of the file,
  10529.  with a structure called the device driver header at its beginning. The
  10530.  header contains information about the driver that the OS/2 kernel can
  10531.  inspect when it needs to satisfy an I/O request by an applications program
  10532.  (see Figures 2 and 3); all of the device drivers in the system are
  10533.  chained together through pointers that are stored in the device driver
  10534.  headers.
  10535.  
  10536.  A driver has two major components: the strategy routine and the interrupt
  10537.  routine. Although they have the same names as the two most important
  10538.  sections of an MS-DOS installable driver, they function in a very different
  10539.  manner.
  10540.  
  10541.  
  10542.  The Strategy Routine
  10543.  
  10544.  The OS/2 kernel will initiate an I/O operation by calling the driver's
  10545.  strategy routine (whose offset is found in the device driver header),
  10546.  passing the address of an information packet called a request header in
  10547.  registers ES:BX. The first 13 bytes of the request header have a fixed
  10548.  format and are followed by data that varies according to the operation
  10549.  being requested (see Figure 4).
  10550.  
  10551.  The most crucial field of the request header is the command code, which
  10552.  selects a particular driver subfunction (see Figure 5). Many of the
  10553.  subfunctions can be carried out immediately at the time of the strategy
  10554.  call; in such cases, the driver simply places any necessary information to
  10555.  be returned to the kernel into the request header and sets the done bit in
  10556.  the status word.
  10557.  
  10558.  However, some subfunctions, such as read and write, involve an
  10559.  unpredictable delay and cannot be completely carried out during the
  10560.  strategy call without an adverse impact on overall system performance.
  10561.  The request headers for such subfunctions are chained onto the driver
  10562.  queue or work list, the actual I/O operation is started if the physical
  10563.  device happens to be idle, and then the strategy routine returns control
  10564.  to the kernel so that other work within the system can go forward.
  10565.  Incidentally, block device drivers are expected to sort their pending
  10566.  work list so as to minimize the average transfer time.
  10567.  
  10568.  The driver strategy routine normally executes at the highest privilege
  10569.  level (Ring 0), with the exception of the initialization subfunction,
  10570.  which executes at Ring 2 (the same as an application with I/O privilege).
  10571.  The strategy routine must be fully reentrant and capable of processing
  10572.  multiple requests simultaneously.
  10573.  
  10574.  
  10575.  The Interrupt Routine
  10576.  
  10577.  The driver interrupt routine is entered from the kernel as a result of a
  10578.  hardware interrupt from the physical device, when an I/O operation is
  10579.  either finished or has been aborted due to a hardware error. The
  10580.  interrupt routine queries the peripheral controller to determine the
  10581.  outcome of the I/O operation, sets the done bit and the appropriate
  10582.  status and error information in the request header, removes the request
  10583.  header from the work list, and notifies the kernel that the operation is
  10584.  complete.
  10585.  
  10586.  The interrupt routine then examines the driver's work list to determine
  10587.  whether other I/O operations are pending for the device. If additional
  10588.  request headers are waiting, the interrupt routine initiates the
  10589.  operation that is at the head of the queue before returning control to
  10590.  the kernel by clearing the carry flag and issuing a FAR RET. In cases
  10591.  where several devices (and their drivers) share the same interrupt level,
  10592.  the kernel will call each driver's interrupt routine in turn until one
  10593.  indicates its acceptance of the interrupt by clearing the carry flag; the
  10594.  other interrupt routines must return with the carry flag set.
  10595.  
  10596.  Since hardware interrupts are by nature asynchronous and unpredictable, an
  10597.  OS/2 device driver must be capable of properly servicing an interrupt in
  10598.  either protected mode or real mode. In order to assist the driver, OS/2
  10599.  always provides special bimodal pointers to request headers-pointers that
  10600.  are valid in either real mode or protected mode. This is accomplished by
  10601.  reserving an area of physical memory to hold request headers and a set of
  10602.  selector values such that the selector for each header is exactly the
  10603.  same as its segment (paragraph) address. As an extra aid to the driver,
  10604.  OS/2 locks down memory segments that are the source or destination of a
  10605.  data transfer and converts their segment:offset or selector:offset
  10606.  addresses into linear 32-bit physical addresses before passing the read or
  10607.  write request to the driver.
  10608.  
  10609.  
  10610.  Unique Aspects
  10611.  
  10612.  Three particularly interesting features of OS/2 device drivers have no
  10613.  counterpart under MS-DOS: the necessity to provide ROM BIOS-equivalent
  10614.  services for real-mode applications, support for device monitors, and
  10615.  the ability of a driver to deinstall itself on demand.
  10616.  
  10617.  The ROM BIOS requirement stems from the propensity of many popular MS-DOS
  10618.  application programs to call the ROM BIOS directly rather than perform
  10619.  their I/O in a well-behaved manner through documented MS-DOS services.
  10620.  The most frequent reason for such ROM BIOS calls is to improve performance
  10621.  or because MS-DOS does not offer the necessary functionality (EGA palette
  10622.  programming, for example). Since the ROM BIOS code is mostly incompatible
  10623.  with the constraints of a protected-mode multitasking environment──it is
  10624.  not reentrant and it frequently masks off interrupts, among other
  10625.  problems──an OS/2 driver must capture the interrupt vectors that invoke
  10626.  ROM BIOS services for its device. It can then either interlock the ROM
  10627.  BIOS routines to prevent interference with the execution of protected-
  10628.  mode applications or substitute a new set of routines that provide
  10629.  equivalent services.
  10630.  
  10631.  Device monitors are an OS/2 innovation that allow keyboard enhancers,
  10632.  macro-expanders, print spoolers, and similar utilities to be written and
  10633.  installed in a hardware-independent manner. An applications program
  10634.  that wishes to filter the data stream of a particular character device
  10635.  driver can issue an OS/2 Application Program Interface (API) function call
  10636.  to register as a monitor for that device. Then the OS/2 kernel acts as
  10637.  the intermediary and asks the driver if it wishes to accept the monitor
  10638.  registration. If the driver assents, it will cooperate with the kernel to
  10639.  pass each character it reads from or writes to the device through a
  10640.  buffer belonging to the monitor program, which can add, delete, or
  10641.  substitute characters at its discretion.
  10642.  
  10643.  Finally, extension of the device driver definition with the DeInstall
  10644.  command code provides efficient use of memory and a more controlled
  10645.  environment for character drivers. A character device driver, under MS-
  10646.  DOS, can be superseded by simply installing another driver with the same
  10647.  logical device name; the first driver loaded has no way to prevent being
  10648.  superseded, and the memory it occupied is simply lost. Under OS/2, if a
  10649.  driver is loaded that has the same logical name as a previously loaded
  10650.  driver, the kernel first sends a request header with the DeInstall command
  10651.  code to the previous driver. The first driver can then release its
  10652.  interrupt vectors and memory and return a success status, after which
  10653.  the new driver is allowed to initialize itself. Alternatively, if the
  10654.  first driver refuses the DeInstall command by returning an error code,
  10655.  the kernel aborts the installation of the new driver with the same name.
  10656.  
  10657.  
  10658.  Device Driver Helpers
  10659.  
  10660.  Under MS-DOS, device drivers must be self-contained. They can use a very
  10661.  limited subset of the Interrupt 21H function calls during their
  10662.  initialization (character output, get MS-DOS version, get or set
  10663.  interrupt vector), but once the system is fully loaded and running, device
  10664.  drivers are not allowed to invoke any MS-DOS system services at all. MS-
  10665.  DOS, as a single-tasking operating system, assumes that only one I/O
  10666.  request will be in progress at any given time. If a driver that is in the
  10667.  process of carrying out an I/O request from the kernel in turn requests a
  10668.  different operation from the ker-nel, the context of the original
  10669.  request is destroyed, and the system will crash.
  10670.  
  10671.  OS/2, on the other hand, was designed specifically for multitasking, and
  10672.  most of its internal routines are fully reentrant. As a result, OS/2
  10673.  provides a battery of services to device drivers, called device driver
  10674.  helpers, or DevHlps, much as it does to application programs, although the
  10675.  nature of these services is of course much different. Moving
  10676.  functionality that all drivers need into the kernel makes drivers
  10677.  smaller, simpler, and easier to debug and ensures that critical driver
  10678.  activities are carried out uniformly and efficiently.
  10679.  
  10680.  Unlike the kernel's API, in which parameters are passed on the stack and
  10681.  each function has a distinct, named entry point, the DevHlp interface uses
  10682.  a register-based parameter-passing convention and a common entry point.
  10683.  The driver is provided with a bimodal pointer to the DevHlp entry point in
  10684.  the request packet it receives from the kernel during its initialization.
  10685.  
  10686.  When the DevHlp entry point is called, a particular DevHlp function is
  10687.  selected by the value in register DL. Additional parameters and addresses
  10688.  are passed in other general registers as demanded by the particular function
  10689.  being requested. The status of a DevHlp function is usually returned to the
  10690.  driver in the zero or carry flags, error codes in register AX, and addresses
  10691.  in registers DS:SI or ES:DI (see Figure 6 for an example of a
  10692.  typical DevHlp call).
  10693.  
  10694.  ───────────────────────────────────────────────────────────────────────────
  10695.  Also see:
  10696.  Summary of the way Device Drivers Queue and Process I/O Requests
  10697.  ───────────────────────────────────────────────────────────────────────────
  10698.  
  10699.  DevHlps and Driver Modes
  10700.  
  10701.  Whenever an OS/2 device driver has control of the CPU, it executes in one
  10702.  of four different modes or contexts: kernel mode, interrupt mode, user
  10703.  mode, and init mode. Understanding these modes and their implications is
  10704.  essential to the task of writing an OS/2 driver, since the DevHlp
  10705.  services available to a driver are determined by its current mode.
  10706.  
  10707.  A driver is in kernel mode when its strategy routine is called by the
  10708.  kernel, usually as the immediate result of an I/O request by an application.
  10709.  The driver executes at the highest privilege level (Ring 0). Nearly every
  10710.  DevHlp service is available to the driver in kernel mode.
  10711.  
  10712.  The driver executes in interrupt mode as the result of a hardware
  10713.  interrupt. The OS/2 kernel fields the interrupt, saves all registers, and
  10714.  enters the driver's interrupt routine via a far call. The driver executes
  10715.  at the highest privilege level (Ring 0). A somewhat restricted set of
  10716.  DevHlp services is available to a driver in interrupt mode.
  10717.  
  10718.  The driver executes in user mode when it is entered as a result of a
  10719.  software interrupt by an application executing in the real-mode session
  10720.  (sometimes called the DOS 3.x compatibility box). The only drivers
  10721.  that must concern themselves with this mode are those that provide
  10722.  equivalents to the original IBM(R) PC ROM BIOS services in a manner
  10723.  consistent with the multitasking and memory protection requirements of
  10724.  OS/2, such as the keyboard, video, or floppy disk driver. Since a driver
  10725.  only runs in user mode when the CPU is in real mode, the concept of
  10726.  privilege levels is irrelevant, but you can think of the driver as
  10727.  executing at the highest privilege level since no protection mechanisms
  10728.  are operative. Relatively few of the DevHlp services are available to
  10729.  the driver in user mode.
  10730.  
  10731.  A driver runs in init mode only once: when its strategy routine is called
  10732.  with a request packet containing the initialization command code,
  10733.  immediately after the driver is loaded into memory during the system boot
  10734.  process. In init mode, the driver runs as though it were an application
  10735.  program IOPL segment, that is, at the next-to-lowest privilege level
  10736.  (Ring 2). A limited set of dynamic-link API calls, mostly concerned with
  10737.  file management (DosOpen, DosRead, DosWrite, and so on), as well as a
  10738.  subset of the DevHlp services (mainly those associated with hardware and
  10739.  memory management), are available to a driver in init mode.
  10740.  
  10741.  Task time and interrupt time are two other terms in the OS/2 device driver
  10742.  documentation regarding driver execution context. A driver is executing
  10743.  at task time when it is called synchronously as the direct result of
  10744.  some application program API request or real-mode software interrupt;
  10745.  this includes both the user mode and kernel mode contexts described
  10746.  above. Interrupt time refers to driver routines being invoked
  10747.  asynchronously as the result of an external hardware event; consequently,
  10748.  the identity of the currently executing process and the state of the CPU
  10749.  (real mode or protected mode) is unpredictable.
  10750.  
  10751.  
  10752.  Categories of DevHlps
  10753.  
  10754.  The roughly 40 DevHlp functions fall into nine categories (see
  10755.  Figure 7): process management, memory management, hardware management,
  10756.  request packet management, character buffer management, timer management,
  10757.  monitor management, semaphore management, and ABIOS communication
  10758.  (IBM PS/2(TM) only).
  10759.  
  10760.  Some of these categories of services are used by every OS/2 device driver,
  10761.  because they allow the driver to perform overlapped, interrupt-driven
  10762.  I/O operations in a well-behaved manner, or they provide access to memory
  10763.  addresses that the driver would otherwise be unable to reach. The others
  10764.  are present only for convenience or to assist in the construction of
  10765.  unusual or highly complex drivers. A more detailed look at the most
  10766.  commonly used groups of DevHlp functions also brings up some interesting
  10767.  points about the operation of OS/2 device drivers and their relationship
  10768.  to the kernel.
  10769.  
  10770.  
  10771.  Process Management
  10772.  
  10773.  The basic technique by which a driver performs overlapped or asynchronous
  10774.  I/O is very simple. When the strategy routine of the driver is called, it
  10775.  sets up the I/O operation (or adds it to the list of pending operations,
  10776.  if one is already in progress) and simply returns to the OS/2 kernel
  10777.  with the done bit cleared in the request header status word. The kernel
  10778.  then suspends the thread that initiated the I/O request. (For more
  10779.  information about OS/2 threads and processes, see "OS/2 Multitasking:
  10780.  Exploiting the Protected Mode of the 80286," MSJ, Vol. 2 No. 2, p. 27.)
  10781.  
  10782.  When the I/O operation is eventually completed, the driver's interrupt
  10783.  routine updates the request packet associated with the operation with the
  10784.  appropriate status word and any other necessary information and then
  10785.  calls the DevHlp routine DevDone. This signals the kernel to awaken the
  10786.  thread that owns the I/O request, which can then perform any necessary
  10787.  post-I/O processing, such as translating the driver's status information
  10788.  into appropriate values to be returned to an application program.
  10789.  
  10790.  A driver can also use the DevHlps Block and Run to suspend a thread while
  10791.  an I/O operation is in progress, particularly if there is a lengthy
  10792.  amount of code in the driver that must be executed after the operation is
  10793.  complete. Putting such code in the strategy routine is preferable to
  10794.  putting it in the interrupt routine, since other lower-priority
  10795.  interrupts are locked out by the hardware while the interrupt routine is
  10796.  executing. The strategy routine sets up the I/O operation and then calls
  10797.  Block with a 32-bit identifier of its own choosing. When the Interrupt
  10798.  routine notes that the I/O operation is complete, it calls Run with the
  10799.  same 32-bit identifier. This will cause the kernel to wake up the original
  10800.  thread within the strategy routine, which can proceed with any necessary
  10801.  post-I/O processing and then exit back to the kernel with the request
  10802.  header's done bit set. The biggest problem with this approach is ensuring
  10803.  that the 32-bit value used in a pair of Block and Run calls is unique
  10804.  within the system, since Run wakes up all threads that have blocked with
  10805.  that particular 32-bit identifier.
  10806.  
  10807.  The DevHlp services Yield and TCYield are provided for drivers that need
  10808.  to transfer long strings of data from one memory location to another, such
  10809.  as a RAMdisk, or whose devices do not support interrupts. A call to Yield
  10810.  simply tells the kernel's scheduler to give any threads with the same or
  10811.  higher priority a chance to run before returning control to the
  10812.  currently executing thread, while TCYield allows only those threads
  10813.  that have a time-critical priority to take a turn. A driver can check the
  10814.  value of YieldFlag or TCYieldFlag (two variables whose addresses are
  10815.  returned by GetDOSVar, another DevHlp) to determine whether any other
  10816.  threads are waiting for the CPU and bypass the call to Yield or TCYield if
  10817.  no other thread is eligible to execute. Such a check along with a possible
  10818.  Yield should be made at least every 3 milliseconds to avoid interference
  10819.  with the proper operation of other drivers.
  10820.  
  10821.  
  10822.  Hardware Management
  10823.  
  10824.  Since the strategy and interrupt routines of device drivers execute at
  10825.  the highest privilege level (Ring 0), they have a free hand with the
  10826.  machine if they want it. As a result, two different drivers that access
  10827.  the same hardware addresses, such as a serial mouse driver and a
  10828.  communications driver that are both configured for COM2, can theoretically
  10829.  come into conflict. However, OS/2 has DevHlps to arbitrate interrupt
  10830.  vector and 8259 Programmable Interrupt Controller (PIC) usage for
  10831.  cooperating drivers. Well-behaved drivers will use these DevHlps to gain
  10832.  access to the hardware resources they need.
  10833.  
  10834.  The DevHlp services SetIRQ and UnsetIRQ assist drivers in capturing or
  10835.  releasing interrupt vectors. When a driver calls SetIRQ to hook its
  10836.  interrupt handler to a given interrupt request (IRQ) level, it also
  10837.  specifies whether it is willing to share the level with another driver.
  10838.  If it specifies that it wants to own the interrupt exclusively, subsequent
  10839.  requests for the same IRQ level by other drivers will fail; if another
  10840.  driver has already signed up for the interrupt, the request by the current
  10841.  driver will fail.
  10842.  
  10843.  When a hardware interrupt occurs, a dispatcher in the OS/2 kernel
  10844.  initially receives control, saves all registers, then enters the driver's
  10845.  interrupt handler via a far call. When several drivers share an
  10846.  interrupt level, the dispatcher calls each driver's handler in turn.
  10847.  The handler must access its device and determine whether that device
  10848.  caused the interrupt. If the driver finds that its own device did not
  10849.  trigger the interrupt, it returns to the kernel with the carry flag set.
  10850.  On the other hand, if the handler finds that it owns the interrupt, it
  10851.  must service its device, possibly start another I/O operation if any are
  10852.  waiting, call the DevHlp service EOI to dismiss the interrupt at the 8259
  10853.  PIC, and then return to the kernel with the carry flag clear. Note that,
  10854.  although drivers can directly access their own device's ports,
  10855.  manipulation of the PIC should be reserved for the kernel to isolate
  10856.  drivers from the interrupt architecture.
  10857.  
  10858.  
  10859.  Memory Management
  10860.  
  10861.  A driver specifies the amount of storage to reserve for its code and data
  10862.  segments in the information it returns to the kernel at load time. Later
  10863.  on, if the driver needs more memory for tables, buffers, or other data
  10864.  structures, it can use the DevHlp services AllocPhys and FreePhys to
  10865.  dynamically allocate and release such memory. The memory that is assigned to
  10866.  the driver by AllocPhys is not movable or swappable, and the driver can
  10867.  specify whether it wants the memory located above or below the 1Mb boundary.
  10868.  
  10869.  One of the trickiest aspects of an OS/2 driver's operation is memory
  10870.  addressing. Since a driver must be able to execute and handle interrupts
  10871.  equivalently in either protected mode or real mode, it might encounter
  10872.  three types of addresses: protected-mode selector:offset pairs, real-
  10873.  mode segment:offset pairs, and the linear or physical 32-bit addresses
  10874.  used by DMA channels and some devices. The operating system's virtual
  10875.  memory manager, which may shuffle segments around to collect unused
  10876.  fragments together or swap segments out to disk, must also be taken into
  10877.  account.
  10878.  
  10879.  The OS/2 kernel takes several measures to shield drivers from most of the
  10880.  potential problems with memory addressing. First, whenever the kernel
  10881.  requests an explicit transfer operation from a driver with a request
  10882.  packet containing the read or write command codes, it passes the driver a
  10883.  physical 32-bit address that has already been "locked," that is, the
  10884.  virtual memory manager has been notified that the memory in question
  10885.  should not be moved or swapped. When the kernel passes the driver an
  10886.  address of a structure (such as a request packet) or a procedure (such as
  10887.  the DevHlp entry point) that the driver will need to access directly, it
  10888.  arranges things so that the address is valid in either real mode or
  10889.  protected mode, that is, the protected-mode selector for the structure or
  10890.  procedure is the same as its real-mode segment. Such an address is called
  10891.  a bimodal pointer.
  10892.  
  10893.  Next, the kernel provides six DevHelp functions which assist a driver by
  10894.  converting physical 32-bit addresses to virtual (segment:offset or
  10895.  selector:offset) addresses and back again: AllocateGDTSelector,
  10896.  PhysToGDTSelector, PhysToUVirt, UnPhysToVirt, and VirtToPhys. The first
  10897.  three are used to access static structures (such as memory allocated by
  10898.  AllocPhys or an adapter's memory-mapped I/O locations). The remainder are
  10899.  used when a driver needs temporary access to data that is transient or
  10900.  belongs to a process. The PhysToVirt service is particularly interesting,
  10901.  because it permits a driver which is executing in real mode to address
  10902.  memory locations above the 1Mb boundary.
  10903.  
  10904.  If the driver is running on a PC/AT(R) or compatible and has to reach
  10905.  extended memory in real mode, PhysToVirt uses the undocumented LOADALL
  10906.  instruction to set the CPU's "shadow" addressing registers, which are
  10907.  normally loaded invisibly from a descriptor table when a segment register
  10908.  is loaded, so as to allow the memory access. On a PS/2, PhysToVirt takes
  10909.  advantage of hardware support for faster mode switching and simply
  10910.  switches the CPU into protected mode, providing the driver with a selector
  10911.  to the memory. When the driver is finished with the virtual address it
  10912.  obtained from PhysToVirt, it calls the DevHlp UnPhysToVirt so that the
  10913.  selector can be reused, at which time the kernel will also restore the
  10914.  original CPU mode if necessary.
  10915.  
  10916.  Finally, for cases in which a memory address is passed to a driver in an
  10917.  IOCTL data packet or by some other channel of communication, the OS/2
  10918.  kernel provides the DevHlp services Lock, Unlock, and VerifyAccess. The
  10919.  driver first calls VerifyAccess with the selector, offset, and length of
  10920.  the memory involved in the requested operation to make sure that the
  10921.  application is really entitled to access the memory. If VerifyAccess
  10922.  succeeds, the driver calls the Lock function to immobilize the segment
  10923.  and VirtToPhys to obtain the physical address, and then proceeds with the
  10924.  I/O operation, calling Unlock afterwards to put the segment back under
  10925.  control of the system's memory manager.
  10926.  
  10927.  
  10928.  Monitor Management
  10929.  
  10930.  Under MS-DOS, terminate-and-stay-resident (TSR) utilities have been
  10931.  tremendous commercial successes. Such utilities intercept keystrokes at
  10932.  the hardware or BIOS level while other applications are running and take
  10933.  some special action when a particular key, called a hot key, is detected.
  10934.  In order to make such utilities possible in protected mode, OS/2 allows
  10935.  applications to register as a monitor for a specific character device,
  10936.  if supported by the device driver. The raw data stream from the device is
  10937.  then passed through a pair of buffers belonging to the application before
  10938.  it is transferred from the driver to the kernel; the application can
  10939.  delete, add, or remap characters in the data stream as it chooses or
  10940.  simply watch for its hot key.
  10941.  
  10942.  As you might expect, a driver's support for monitors involves a rather
  10943.  complicated interplay of calls from the application to the kernel, the
  10944.  kernel to the driver, and the driver to the kernel, and requires a high
  10945.  degree of cooperation among the three to preserve the integrity of the
  10946.  system. A special kernel module, called the monitor dispatcher,
  10947.  arbitrates among the three components and takes care of moving data from
  10948.  the driver through the buffers of one or more monitors and then back to
  10949.  the driver.
  10950.  
  10951.  An application registers as a monitor for a character device by calling
  10952.  DosMonOpen with the logical name of the device. It then calls DosMonReg
  10953.  with the monitor handle returned by DosMonOpen and the addresses of two
  10954.  buffers that will be used for input and output of data packets from the
  10955.  driver. If registration succeeds, the application becomes an integral
  10956.  part of the data stream and must use the functions DosMonRead and
  10957.  DosMonWrite to pass the character data through its buffers in a timely
  10958.  fashion. The application uses the function DosMonClose to terminate its
  10959.  connection with the device driver, after which it is removed from the
  10960.  data stream.
  10961.  
  10962.  From the driver's point of view, it informs the kernel that it is prepared
  10963.  to support monitors by issuing the DevHlp call MonitorCreate, which
  10964.  establishes an initial "empty chain" of device monitors and assigns a
  10965.  monitor handle to it. The driver may choose to create the monitor chain
  10966.  during its initialization or wait until there is a need for it. The
  10967.  driver is informed that an application is attempting to register as a
  10968.  monitor by the receipt of a special IOCTL request packet (Category 0AH
  10969.  Function 40H); it then calls the DevHlp service Register, which causes the
  10970.  kernel's monitor dispatcher to insert the application's buffers into the
  10971.  monitor chain at the appropriate point.
  10972.  
  10973.  Once the monitor chain is created, the driver must pass each character it
  10974.  receives from the device through the chain by calling the DevHlp service
  10975.  MonWrite. A notification routine within the driver is called by the
  10976.  kernel's monitor dispatcher when data emerges from the end of the monitor
  10977.  chain. The driver passes the data onward to the kernel in the usual
  10978.  manner. It is then distributed to applications via the API function
  10979.  DosRead.
  10980.  
  10981.  When the driver is informed by the kernel that an application has issued
  10982.  DosMonClose, the driver calls the DevHlp service DeRegister with the
  10983.  application's PID, resulting in the monitor dispatcher removing the
  10984.  application's buffers from the monitor chain. When the driver notes that
  10985.  all monitor connections are closed, it can call the DevHlp MonitorCreate
  10986.  again with a parameter that causes the monitor chain to be destroyed.
  10987.  
  10988.  
  10989.  Other Services
  10990.  
  10991.  The DevHlp services listed under semaphore management in Figure 7 allow a
  10992.  driver to communicate directly with an application via a system
  10993.  semaphore. Of course, a driver cannot create or open a system semaphore
  10994.  using the normal API calls, but if an application passes the handle for a
  10995.  system semaphore to the driver in an IOCTL packet, the driver can use the
  10996.  DevHlp service SemHandle at task time to obtain a new handle for the
  10997.  semaphore for use at interrupt time. The DevHlp functions SemRequest and
  10998.  SemClear, which the driver can call to obtain or release ownership of
  10999.  the semaphore, are the equivalents of the API functions DosSemRequest
  11000.  and DosSemClear.
  11001.  
  11002.  The Timer DevHlps assist a driver in performing polled I/O without
  11003.  degrading the system for devices that do not support interrupts or permit
  11004.  a driver to set up a watchdog timer to detect lost interrupts or I/O
  11005.  operations that never complete. The function SetTimer is called with the
  11006.  address of a routine within the driver that should be entered on each
  11007.  timer tick; this is the equivalent of chaining onto Interrupt 1CH (the
  11008.  ROM BIOS timer tick vector) under MS-DOS. The DevHlp service TickCount is
  11009.  a more general version of SetTimer; it also allows the number of ticks
  11010.  between entries to the driver's timer tick handler to be specified. The
  11011.  driver can remove its timer tick handler from the system's list of all
  11012.  such handlers by calling ResetTimer.
  11013.  
  11014.  The other device helper services which have not already been listed play
  11015.  minor roles, and are present mostly for convenience. For instance, the
  11016.  DevHlps listed under Request Packet Management in Figure 7 assist the
  11017.  driver in maintaining and sorting a linked list of request headers for
  11018.  pending I/O operations, but do not provide any functionality that the
  11019.  driver couldn't do for itself with a little extra code. Similarly, the
  11020.  DevHlps listed under Character Buffer Management provide simple support
  11021.  for a ring buffer of characters that are received or transmitted from the
  11022.  device.
  11023.  
  11024.  
  11025.  A Skeleton Device Driver
  11026.  
  11027.  As an illustration of the essential elements of an OS/2 device driver,
  11028.  TEMPLATE.ASM (see Figure 8) has the source code for a skeleton character
  11029.  driver called TEMPLATE.SYS. This driver performs no useful function; it
  11030.  simply returns a success code for all request packets that the kernel
  11031.  passes to it. However, TEMPLATE demonstrates the segment ordering and
  11032.  naming conventions that should be used in an installable driver, the use
  11033.  of API calls at initialization time, and a method by which initialization
  11034.  code and data can be discarded to minimize a driver's memory
  11035.  requirements. TEMPLATE can serve as a starting point for your own
  11036.  installable device drivers.
  11037.  
  11038.  In order to assemble and link TEMPLATE.SYS, you use the source file
  11039.  TEMPLATE.ASM, the module definition file TEMPLATE.DEF (see Figure 9),
  11040.  the Microsoft Macro Assembler, and the Microsoft Segmented Object Linker.
  11041.  Enter the commands shown in Figure 10.
  11042.  
  11043.  If the assembly and link are successful, you can copy the file
  11044.  TEMPLATE.SYS to the root directory of your boot disk, add
  11045.  DEVICE=TEMPLATE.SYS to the CONFIG.SYS file, and then restart the system
  11046.  to see the TEMPLATE driver's sign-on message.
  11047.  
  11048.  
  11049.  Summary
  11050.  
  11051.  OS/2 device drivers are architecturally and functionally far more complex
  11052.  than their MS-DOS ancestors, principally because they need to support
  11053.  bimodal operation and fully interrupt-driven I/O. This article has
  11054.  provided a brief overview of OS/2 device driver structure and
  11055.  capabilities.
  11056.  
  11057.  
  11058.  Figure 1:  Structure of an OS/2 installable device driver file. OS/2 drivers
  11059.             are linked as small-model EXE files, containing one code and one
  11060.             data segment. By convention, driver files always have the
  11061.             extension .SYS. Note that code and data used only by the driver's
  11062.             initialization routine can be positioned at the segments and
  11063.             discarded after driver initialization is completed to minimize the
  11064.             driver's memory overhead.
  11065.  
  11066.              Start  ╔═════════════════════════════════╗ ──────┐
  11067.              of file║         EXE file header         ║       │
  11068.                     ╠═════════════════════════════════╣       │
  11069.                     ║       Device driver header      ║    Driver
  11070.                     ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣     data
  11071.                     ║                                 ║    segment
  11072.                     ║       Resident driver data      ║       │
  11073.                     ║                                 ║       │
  11074.                     ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣ ══════╡
  11075.                     ║ Initialization data (discarded) ║       │
  11076.                     ║                                 ║       │
  11077.                     ╠═════════════════════════════════╣    Driver
  11078.                     ║      Resident driver code       ║     code
  11079.                     ║                                 ║    segment
  11080.              End    ╠═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═╣       │
  11081.              of file║ Initialization code (discarded) ║       │
  11082.                     ╚═════════════════════════════════╝ ──────┘
  11083.  
  11084.  
  11085.  Figure 2:  OS/2 device driver header.
  11086.  
  11087.  In character device drivers, the name or units field contains the logical
  11088.  name of the device, left-justified and padded with spaces; in block device
  11089.  drivers, the first byte of the field contains the number of logical units
  11090.  supported by the driver (filled in by OS/2), followed by 7 bytes of reserved
  11091.  space.
  11092.  
  11093.   offset
  11094.        0┌──────────────────────────────────────────┐
  11095.         │    link to next driver header, offset    │
  11096.        2├──────────────────────────────────────────┤
  11097.         │   link to next driver header, selector   │
  11098.        4├──────────────────────────────────────────┤
  11099.         │          device attribute word           │
  11100.        6├──────────────────────────────────────────┤
  11101.         │       offset, strategy entry point       │
  11102.        8├──────────────────────────────────────────┤
  11103.         │                 reserved                 │
  11104.      0AH├──────────────────────────────────────────┤
  11105.         │                                          │
  11106.         │         name or units (8 bytes)          │
  11107.         │                                          │
  11108.      12H├──────────────────────────────────────────┤
  11109.         │                                          │
  11110.         │            reserved (8 bytes)            │
  11111.         │                                          │
  11112.         └──────────────────────────────────────────┘
  11113.  
  11114.  
  11115.  Figure 3:  The device attribute word in the device driver header, which
  11116.             describes the characteristics and capabilities of the associated
  11117.             driver.
  11118.  
  11119. ╓┌─────────┌─────────────────────────────────────────────────────────────────╖
  11120.  Bit(s)    Meaning
  11121.  
  11122.  15        = 1 if character device
  11123.            = 0 if block device
  11124.  
  11125.  14        = 0 (reserved)
  11126.  
  11127.  13        = 1 if non-IBM-compatible format (block devices)
  11128.            = 0 if IBM-compatible format (that is, FAT ID byte is valid)
  11129.  
  11130.  12        = 1 if device name is not to be protected by "sharer" module
  11131.               (character devices only)
  11132.  Bit(s)    Meaning
  11133.              (character devices only)
  11134.            = 0 if sharer controls contention for the device
  11135.  
  11136.  11        = 1 if Device Open/Close functions (character devices) or
  11137.               Removable Media function (block devices) supported
  11138.            = 0 if Device Open/Close/Removable Media not supported
  11139.  
  11140.  10        = 0 (reserved)
  11141.  
  11142.  7-9       = function level, 001 to indicate OS/2 device driver
  11143.  
  11144.  4-6       = 0 (reserved)
  11145.  
  11146.  3         = 1 if current CLOCK device
  11147.  
  11148.  2         = 1 if current NUL device
  11149.  
  11150.  1         = 1 if current standard output device (stdout)
  11151.  
  11152.  0         = 1 if current standard input device (stdin)
  11153.  Bit(s)    Meaning
  11154. 0         = 1 if current standard input device (stdin)
  11155.  
  11156.  
  11157.  
  11158.  Figure 4:  A prototype OS/2 device driver request header.
  11159.  
  11160.  The OS/2 kernel requexts a driver operation by passing the bimodal address
  11161.  of a request header containing operation-specific parameters, and the driver
  11162.  communicates the results of the operation back to the kernel by placing
  11163.  status and other information in the same request header. In order to support
  11164.  multiple concurrent I/O requests, the driver maintains a private queue of
  11165.  request headers for its pending operations. This is done with the aid of the
  11166.  kernel DevHlp functions.
  11167.  
  11168.        0┌──────────────────────────────────────────┐
  11169.         │         length of request header         │
  11170.        1├──────────────────────────────────────────┤
  11171.         │         block device unit number         │
  11172.        2├──────────────────────────────────────────┤
  11173.         │    command code (driver subfunction)     │
  11174.        3├──────────────────────────────────────────┤
  11175.         │             returned status              │
  11176.         │                                          │
  11177.        5├──────────────────────────────────────────┤
  11178.         │         reserved area (4 bytes)          │
  11179.         │                                          │
  11180.        9├──────────────────────────────────────────┤
  11181.         │         queue linkage (4 bytes)          │
  11182.         │                                          │
  11183.      0DH├──────────────────────────────────────────┤
  11184.         │                                          │
  11185.         │  command-specific data (length varies)   │
  11186.         │                                          │
  11187.         └──────────────────────────────────────────┘
  11188.  
  11189.  
  11190.  Figure 5:  Command Codes and Their Associated Driver Functions
  11191.  
  11192. ╓┌────────┌──────────────────┌───────────────────────────────────────────────
  11193.  Command
  11194.  Code     Subfunction Name   Subfunction Action
  11195.  Command
  11196.  Code     Subfunction Name   Subfunction Action
  11197.  
  11198.  0        Init               Initialize driver (called at driver load time).
  11199.                              Tests for existence and operability of
  11200.                              peripheral device register interrupt handler and
  11201.                              returns size of resident code and data segments
  11202.                              to kernel. Block drivers also return number of
  11203.                              units supported and an array of pointers to BIOS
  11204.                              Parameter Blocks (BPBs).
  11205.  
  11206.  1        Media Check        Returns a code indicating whether the media has
  11207.                              been changed.
  11208.  
  11209.  2        Build BPB          Returns a pointer to the BIOS Parameter Block
  11210.                              for the indicated disk type. The BPB for a disk
  11211.                              describes its physical characteristics (sector
  11212.                              size, number of tracks, tracks per sector, and
  11213.                              so on).
  11214.  
  11215.  3        Reserved
  11216.  Command
  11217.  Code     Subfunction Name   Subfunction Action
  11218. 3        Reserved
  11219.  
  11220.  4        Read               Transfers data from the device to memory.
  11221.  
  11222.  5        NonDestructive     Returns the next character from the device, if
  11223.           Read               one is available, without removing it from the
  11224.                              input buffer.
  11225.  
  11226.  6        Input Status       Returns a flag indicating whether characters are
  11227.                              waiting in the device's input buffer.
  11228.  
  11229.  7        Input Flush        Discards all data waiting in the device's input
  11230.                              buffer and cancels pending read requests.
  11231.  
  11232.  8        Write              Transfers data from memory to the device.
  11233.  
  11234.  9        Write with Verify  Transfers data from memory to the device,
  11235.                              performing a read-after-write verification if
  11236.                              possible.
  11237.  Command
  11238.  Code     Subfunction Name   Subfunction Action
  11239.                             possible.
  11240.  
  11241.  10       Output Status      Returns a flag indicating whether characters are
  11242.                              waiting in the device's output buffer.
  11243.  
  11244.  11       Output Flush       Discards all data waiting in the device's output
  11245.                              buffer and cancels pending write requests.
  11246.  
  11247.  12       Reserved
  11248.  
  11249.  13       Device Open        Initializes a character device for subsequent
  11250.                              I/O if necessary.
  11251.  
  11252.  14       Device Close       Performs any necessary post-I/O processing (such
  11253.                              as transmitting a form feed to a list device).
  11254.  
  11255.  15       Removable Media    Returns a code indicating whether the media is
  11256.                              removable.
  11257.  
  11258.  Command
  11259.  Code     Subfunction Name   Subfunction Action
  11260. 
  11261.  16       Generic IOCTL      General-purpose mechanism for communication
  11262.                              between the driver and application programs.
  11263.  
  11264.  17       Reset Media        Called by the kernel to acknowledge and clear
  11265.                              the "uncertain media" error condition previously
  11266.                              returned by the driver.
  11267.  
  11268.  18       Get Logical Drive  Returns the drive code for the logical unit
  11269.           Map                currently mapped to the specified physical unit.
  11270.  
  11271.  19       Set Logical Drive  Notifies the driver which logical unit will be
  11272.           Map                used for the next access to the specified
  11273.                              physical unit.
  11274.  
  11275.  20       DeInstall Driver   Signals the driver to release its interrupt
  11276.                              vectors and memory. Called when a character
  11277.                              device driver is superseded by the loading of
  11278.                              another driver with the same logical device
  11279.  Command
  11280.  Code     Subfunction Name   Subfunction Action
  11281.                             another driver with the same logical device
  11282.                              name.
  11283.  
  11284.  21       Reserved
  11285.  
  11286.  22       Partitionable      Returns the number of partitionable fixed disks
  11287.           Fixed Disk         supported by the driver.
  11288.  
  11289.  23       Get Fixed Disk     Returns the identities of the logical disks, if
  11290.           Map                any, supported by driver on the specified hard
  11291.                              disk.
  11292.  
  11293.  24-26    Reserved
  11294.  
  11295.  
  11296.  Figure 6:  Example of a DevHlp call. This code allocates 64Kb of physical
  11297.             memory above the 1Mb boundary for use by the driver. When the
  11298.             driver wishes to access the memory block, it must use PhysToVirt
  11299.             or PhysToUVirt to convert the physical address stored in 'bufptr'
  11300.             into an appropriate virtual address for the current CPU mode.
  11301.  
  11302.  devhlp  dd      ?       ; contains bimodal pointer
  11303.                          ; to DevHlp common entry point
  11304.                          ; (from Init routine)
  11305.  
  11306.  bufptr  dd      ?       ; receives 32-bit physical
  11307.                          ; address of allocated block
  11308.  
  11309.                     .
  11310.                     .
  11311.                     .
  11312.                          ; allocate 64Kb of memory
  11313.                          ; above 1Mb for use by driver
  11314.  
  11315.                          ; AX:BX = size in bytes
  11316.          mov     ax,1    ; AX = high word
  11317.          mov     bx,0    ; BX = low word
  11318.          mov     dh,0    ; 0 = above 1Mb boundary
  11319.          mov     dl,18h  ; DevHlp 18H = AllocPhys
  11320.  
  11321.          call    devhlp  ; indirect call to DevHlp
  11322.                          ; common entry point
  11323.  
  11324.          jc      error   ; jump if allocation failed
  11325.  
  11326.                          ; save 32-bit physical
  11327.                          ; address of memory block
  11328.          mov     word ptr bufptr,bx
  11329.          mov     word ptr bufptr+2,ax
  11330.                     .
  11331.                     .
  11332.                     .
  11333.  
  11334.  
  11335.  Figure 7:  DevHlp services by category, their function numbers, and the
  11336.             driver contents in which they may be used (K = kernel mode, I =
  11337.             interrupt mode, U = user mode, N = initialization mode).
  11338.  
  11339. ╓┌────────────────────┌──────┌──┌──┌──┌──┌───────────────────────────────────╖
  11340.  DevHlp               DevHlp             DevHlp
  11341.  Name                 Code   K  I  U  N  Action
  11342.  DevHlp               DevHlp             DevHlp
  11343.  Name                 Code   K  I  U  N  Action
  11344.  
  11345.  Process Management
  11346.  
  11347.  DevDone              01H    K  I        Signals that the specified operation
  11348.                                          is complete. Kernel unblocks any
  11349.                                          threads waiting for the operation.
  11350.  
  11351.  Block                04H    K     U     Blocks the calling thread until a
  11352.                                          timeout occurs of thread is
  11353.                                          unblocked by Run.
  11354.  
  11355.  Run                  05H    K  I  U     Activates a thread that was
  11356.                                          previously suspended by a call to
  11357.                                          Block.
  11358.  
  11359.  Yield                02H    K           Surrenders the CPU to any other
  11360.                                          threads of the same or higher
  11361.                                          priority that are ready to execute.
  11362.  
  11363.  DevHlp               DevHlp             DevHlp
  11364.  Name                 Code   K  I  U  N  Action
  11365. 
  11366.  TCYield              03H    K           Similar to Yield, but allows other
  11367.                                          threads to execute only if they have
  11368.                                          a time-critical priority.
  11369.  
  11370.  Memory Management
  11371.  
  11372.  AllocPhys            18H    K        N  Allocates a block of fixed (not
  11373.                                          swappable or movable) memory. Caller
  11374.                                          can specify whether the block should
  11375.                                          be above or below the 1Mb boundary.
  11376.  
  11377.  FreePhys             19H    K        N  Releases a block of memory
  11378.                                          previously allocated with AllocPhys.
  11379.  
  11380.  Lock                 13H    K        N  Locks a memory segment, that is,
  11381.                                          flags the segment as nonmovable and
  11382.                                          nonswappable.
  11383.  
  11384.  DevHlp               DevHlp             DevHlp
  11385.  Name                 Code   K  I  U  N  Action
  11386. 
  11387.  Unlock               14H    K  I     N  Unlocks a memory segment.
  11388.  
  11389.  Verify Access        27H    K           Verifies that a user process has
  11390.                                          access to a segment.
  11391.  
  11392.  PhysToVirt           15H    K  I     N  Converts a 32-bit physical (linear)
  11393.                                          address to a virtual address.
  11394.  
  11395.  UnPhysToVirt         32H    K  I     N  Signals that the virtual addresses
  11396.                                          previously obtained with PhysToVirt
  11397.                                          can be reused.
  11398.  
  11399.  PhysToUVirt          17H    K           Converts a 32-bit physical address
  11400.                                          to a virtual address accessed
  11401.                                          through the current Local Descriptor
  11402.                                          Table (LDT).
  11403.  
  11404.  VirtToPhys           16H    K        N  Converts a virtual address
  11405.  DevHlp               DevHlp             DevHlp
  11406.  Name                 Code   K  I  U  N  Action
  11407. VirtToPhys           16H    K        N  Converts a virtual address
  11408.                                          (segment:offset or selector:offset)
  11409.                                          to a 32-bit physical address.
  11410.  
  11411.  AllocateGDTSelector  2DH    K        N  Allocate one or more GDT selectors
  11412.                                          for use by the driver.
  11413.  
  11414.  PhysToGDTSelector    2EH    K        N  Map a physical address and length
  11415.                                          onto a GDT selector.
  11416.  
  11417.  RealToProt           2FH       I        Switch the CPU into protected mode.
  11418.  
  11419.  ProtToReal           30H       I        Switch the CPU into real mode.
  11420.  
  11421.  Hardware Management
  11422.  
  11423.  SetIRQ               1BH    K        N  Sets the interrupt vector for the
  11424.                                          indicated interrupt request (IRQ)
  11425.                                          level to point to the driver's
  11426.  DevHlp               DevHlp             DevHlp
  11427.  Name                 Code   K  I  U  N  Action
  11428.                                         level to point to the driver's
  11429.                                          interrupt handler.
  11430.  
  11431.  UnSetIRQ             1CH    K  I     N  Releases the interrupt vector for
  11432.                                          the specifies IRQ level.
  11433.  
  11434.  EOI                  31H       I     N  Issues an end of interrupt to the
  11435.                                          appropriate 8259 PIC for the
  11436.                                          specified IRQ level.
  11437.  
  11438.  Request Packet Management
  11439.  
  11440.  PushReqPacket        09H    K           Adds a request packet to the
  11441.                                          driver's linked list of packets.
  11442.  
  11443.  SortReqPacket        0CH    K           Inserts a request packet into the
  11444.                                          driver's linked list of packets,
  11445.                                          sorted by sector number.
  11446.  
  11447.  DevHlp               DevHlp             DevHlp
  11448.  Name                 Code   K  I  U  N  Action
  11449. 
  11450.  PullReqPacket        0AH    K  I        Removes the oldest request packet
  11451.                                          from the driver's linked list.
  11452.  
  11453.  AllocReqPacket       0DH    K           Allocates a block of memory large
  11454.                                          enough to hold the largest possible
  11455.                                          request packet and returns a bimodal
  11456.                                          pointer.
  11457.  
  11458.  FreeReqPacket        0EH    K           Releases a request packet previously
  11459.                                          allocated with AllocReqPacket.
  11460.  
  11461.  Character Buffer Management
  11462.  
  11463.  QueueInit            0FH    K  I  U  N  Establishes a ring buffer for
  11464.                                          storage of characters and
  11465.                                          initializes its pointers.
  11466.  
  11467.  QueueRead            12H    K  I  U     Returns and removes a character from
  11468.  DevHlp               DevHlp             DevHlp
  11469.  Name                 Code   K  I  U  N  Action
  11470. QueueRead            12H    K  I  U     Returns and removes a character from
  11471.                                          the specified buffer.
  11472.  
  11473.  QueueWrite           11H    K  I  U     Puts a character into the specified
  11474.                                          buffer.
  11475.  
  11476.  QueueFlush           10H    K  I  U     Resets the pointers to the specified
  11477.                                          buffer.
  11478.  
  11479.  Timer Management
  11480.  
  11481.  SetTimer             1DH    K        N  Adds a timer tick handler to be
  11482.                                          called on every timer tick to the
  11483.                                          system's list of such handlers.
  11484.  
  11485.  TickCount            33H    K  I  U  N  Adds a timer tick handler to be
  11486.                                          called at specified intervals to the
  11487.                                          system's list of such handlers, or
  11488.                                          modifies the interval at which an
  11489.  DevHlp               DevHlp             DevHlp
  11490.  Name                 Code   K  I  U  N  Action
  11491.                                         modifies the interval at which an
  11492.                                          existing handler is called.
  11493.  
  11494.  ResetTimer           1EH    K  I     N  Removes a timer tick handler from
  11495.                                          the system's list of such handlers.
  11496.  
  11497.  Monitor Management
  11498.  
  11499.  MonitorCreate        1FH    K        N  Creates or removes an empty monitor
  11500.                                          chain.
  11501.  
  11502.  Register             20H    K           Adds a process to a monitor chain.
  11503.  
  11504.  MonWrite             22H    K  I  U     Passes a data record to a monitor
  11505.                                          chain for filtering.
  11506.  
  11507.  MonFlush             23H    K           Removes all data from a monitor
  11508.                                          chain.
  11509.  
  11510.  DevHlp               DevHlp             DevHlp
  11511.  Name                 Code   K  I  U  N  Action
  11512. 
  11513.  DeRegister           21H    K           Removes a jprocess from a monitor
  11514.                                          chain.
  11515.  
  11516.  Semaphore Management
  11517.  
  11518.  SemHandle            08H    K  I        Converts a process's handle for a
  11519.                                          system semaphore to a virtual
  11520.                                          address that the driver can use at
  11521.                                          interrupt time.
  11522.  
  11523.  SemRequest           06H    K     U     Claims (sets) a semaphore. If the
  11524.                                          semaphore is already set, blocks the
  11525.                                          thread until the semaphore is
  11526.                                          available.
  11527.  
  11528.  SemClear             07H    K  I  U     Clears a semaphore, restarting any
  11529.                                          threads that were blocking on the
  11530.                                          semaphore.
  11531.  DevHlp               DevHlp             DevHlp
  11532.  Name                 Code   K  I  U  N  Action
  11533.                                         semaphore.
  11534.  
  11535.  Miscellaneous Device Helpers
  11536.  
  11537.  SchedClockAddr       00H    K        N  Obtains the address of the kernel's
  11538.                                          routine to be called on each clock
  11539.                                          tick. Used only by the system's
  11540.                                          clock driver.
  11541.  
  11542.  SendEvent            25H    K  I        Signals the kernal that a critical
  11543.                                          event has been detected, such as the
  11544.                                          Session Manager hot key or a Ctrl-
  11545.                                          Break.
  11546.  
  11547.  GetDOSVar            24H    K        N  Returns a bimodal pointer to a table
  11548.                                          of useful kernel addresses and
  11549.                                          variables.
  11550.  
  11551.  SetROMVector         1AH    K        N  Captures an interrupt vector
  11552.  DevHlp               DevHlp             DevHlp
  11553.  Name                 Code   K  I  U  N  Action
  11554. SetROMVector         1AH    K        N  Captures an interrupt vector
  11555.                                          normally used in real mode to invoke
  11556.                                          a ROM BIOS function, returning the
  11557.                                          previous contents of the vector for
  11558.                                          chaining.
  11559.  
  11560.  ROMCritSection       26H          U     Protects ROM BIOS code from
  11561.                                          interrupts that might cause a
  11562.                                          cantext switch. Called by a driver
  11563.                                          in user mode.
  11564.  
  11565.  ABIOS Communication Device Helpers (IBM PS/2 only)
  11566.  
  11567.  ABIOSCall            36H    K  I  U  N  Invokes an Advanced BIOS (ABIOS)
  11568.                                          service on behalf of the driver.
  11569.  
  11570.  ABIOSCommonEntry     37H    K  I  U  N  Transfers to an ABIOS common entry
  11571.                                          point on behalf of the driver.
  11572.  
  11573.  DevHlp               DevHlp             DevHlp
  11574.  Name                 Code   K  I  U  N  Action
  11575. 
  11576.  GetLIDEntry          34H    K        N  Obtains a logical ID (LID) for a
  11577.                                          physical device for use with
  11578.                                          subsequent ABIOS calls.
  11579.  
  11580.  FreeLIDEntry         35H    K        N  Releases a Logical ID (LID) for a
  11581.                                          physical device.
  11582.  
  11583.  
  11584.  Figure 8:  Installable Driver Template
  11585.  
  11586.  name    template
  11587.  page    55,132
  11588.  title   'TEMPLATE - installable driver template'
  11589.  .286c
  11590.  
  11591.  ;
  11592.  ; TEMPLATE.ASM:  A program skeleton for an installable
  11593.  ;                OS/2 character device driver.
  11594.  ;
  11595.  ; The driver command code routines are stubs only and have
  11596.  ; no effect but to return a nonerror "Done" status.
  11597.  ;
  11598.  ;
  11599.  ;
  11600.  
  11601.  maxcmd  equ     26              ; maximum allowed command code
  11602.  
  11603.  stdin   equ     0               ; standard device handles
  11604.  stdout  equ     1
  11605.  stderr  equ     2
  11606.  
  11607.  cr      equ     0dh             ; ASCII carriage return
  11608.  lf      equ     0ah             ; ASCII line feed
  11609.  
  11610.  
  11611.          extrn   DOSWRITE:far
  11612.  
  11613.  
  11614.  DGROUP  group   _DATA
  11615.  
  11616.  
  11617.  _DATA   segment word public 'DATA'
  11618.  
  11619.  
  11620.                                  ; device driver header...
  11621.  header  dd      -1              ; link to next device driver
  11622.          dw      8880h           ; device attribute word
  11623.          dw      Strat           ; "Strategy" routine entry point
  11624.          dw      0               ; (reserved)
  11625.          db      'TEMPLATE'      ; logical device name
  11626.          db      8 dup (0)       ; (reserved)
  11627.  
  11628.  
  11629.  devhlp  dd      ?               ; DevHlp entry point
  11630.  
  11631.  wlen    dw      ?               ; receives DOSWRITE length
  11632.                                  ; Strategy routine dispatch table
  11633.                                  ; for request packet command code
  11634.  dispch  dw      Init            ; 0  = initialize driver
  11635.          dw      MediaChk        ; 1  = media check on block device
  11636.          dw      BuildBPB        ; 2  = build BIOS parameter block
  11637.          dw      Error           ; 3  = not used
  11638.          dw      Read            ; 4  = read (input) from device
  11639.          dw      NdRead          ; 5  = nondestructive read
  11640.          dw      InpStat         ; 6  = return input status
  11641.          dw      InpFlush        ; 7  = flush device input buffers
  11642.          dw      Write           ; 8  = write (output) to device
  11643.          dw      WriteVfy        ; 9  = write with verify
  11644.          dw      OutStat         ; 10 = return output status
  11645.          dw      OutFlush        ; 11 = flush output buffers
  11646.          dw      Error           ; 12 = not used
  11647.          dw      DevOpen         ; 13 = device open
  11648.          dw      DevClose        ; 14 = device close
  11649.          dw      RemMedia        ; 15 = removable media
  11650.          dw      GenIOCTL        ; 16 = generic IOCTL
  11651.          dw      ResetMed        ; 17 = reset media
  11652.          dw      GetLogDrv       ; 18 = get logical drive
  11653.          dw      SetLogDrv       ; 19 = set logical drive
  11654.          dw      DeInstall       ; 20 = deinstall
  11655.          dw      PortAcc .       ; 21 = reserved
  11656.          dw      PartFD          ; 22 = partitionable fixed disks
  11657.          dw      FDMap           ; 23 = get fixed disk unit map
  11658.          dw      Error           ; 24 = not used
  11659.          dw      Error           ; 25 = not used
  11660.          dw      Error           ; 26 = not used
  11661.  
  11662.  
  11663.  ident   db      cr,lf,lf
  11664.          db      'TEMPLATE Example OS/2 Device Driver'
  11665.          db      cr,lf
  11666.  
  11667.  ident_len equ $-ident
  11668.  
  11669.  _DATA   ends
  11670.  
  11671.  _TEXT   segment word public 'CODE'
  11672.  
  11673.          assume  cs:_TEXT,ds:DGROUP,es:NOTHING
  11674.  
  11675.  Strat   proc    far             ; device driver Strategy routine,
  11676.                                  ; called by OS/2 kernel with
  11677.                                  ; ES:BX = address of request packet
  11678.  
  11679.          mov     di,es:[bx+2]    ; get command code from packet
  11680.          and     di,0ffh
  11681.          cmp     di,maxcmd       ; supported by this driver?
  11682.          jle     Strat1          ; jump if command code OK
  11683.  
  11684.          call    Error           ; bad command code
  11685.          jmp     Strat2
  11686.  
  11687.  Strat1: add     di,di           ; command code * 2
  11688.                                  ; branch to appropriate routine
  11689.          call    word ptr [di+dispch]
  11690.  
  11691.  Strat2:                         ; store subfunction status in
  11692.          mov     es:[bx+3],ax    ; AX into request packet
  11693.  
  11694.          ret                     ; back to OS/2 kernel
  11695.  
  11696.  Strat   endp
  11697.  
  11698.  
  11699.  Intr    proc  far               ; driver Interrupt handler
  11700.  
  11701.          clc                     ; signal that we owned interrupt
  11702.  
  11703.          ret
  11704.  
  11705.  Intr    endp
  11706.  
  11707.  ; Command code routines are called by the Strategy routine
  11708.  ; via the Dispatch table with ES:BX pointing to the request
  11709.  ; header.  Each routine should return ES:BX unchanged,
  11710.  ; and AX = status to be placed in request packet:
  11711.  ;       0100H if 'done' and no error
  11712.  ;       0000H if thread should block pending interrupt
  11713.  ;       81xxH if 'done' and error detected (xx=error code)
  11714.  ;
  11715.  
  11716.  MediaChk proc   near            ; function 1 = Media Check
  11717.  
  11718.          mov     ax,0100h        ; return 'done' status
  11719.          ret
  11720.  
  11721.  MediaChk endp
  11722.  
  11723.  BuildBPB proc   near            ; function 2 = Build BPB
  11724.  
  11725.          mov     ax,0100h        ; return 'done' status
  11726.          ret
  11727.  
  11728.  BuildBPB endp
  11729.  
  11730.  Read    proc    near            ; function 4 = Read (Input)
  11731.  
  11732.          mov     ax,0100h        ; return 'done' status
  11733.          ret
  11734.  
  11735.  Read    endp
  11736.  
  11737.  NdRead  proc    near            ; function 5 = Nondestructive Read
  11738.  
  11739.          mov     ax,0100h        ; return 'done' status
  11740.          ret
  11741.  
  11742.  NdRead  endp
  11743.  
  11744.  InpStat proc    near            ; function 6 = Input Status
  11745.  
  11746.          mov     ax,0100h        ; return 'done' status
  11747.          ret
  11748.  
  11749.  InpStat endp
  11750.  
  11751.  InpFlush proc   near            ; function 7 = Flush Input Buffers
  11752.  
  11753.          mov     ax,0100h        ; return 'done' status
  11754.          ret
  11755.  
  11756.  InpFlush endp
  11757.  
  11758.  Write   proc    near            ; function 8 = Write (Output)
  11759.  
  11760.          mov     ax,0100h        ; return 'done' status
  11761.          ret
  11762.  
  11763.  Write   endp
  11764.  
  11765.  WriteVfy proc   near            ; function 9 = Write with Verify
  11766.  
  11767.          mov     ax,0100h        ; return 'done' status
  11768.          ret
  11769.  
  11770.  WriteVfy endp
  11771.  
  11772.  OutStat proc    near            ; function 10 = Output Status
  11773.  
  11774.          mov     ax,0100h        ; return 'done' status
  11775.          ret
  11776.  
  11777.  OutStat endp
  11778.  
  11779.  OutFlush proc   near            ; function 11 = Flush Output Buffers
  11780.  
  11781.          mov     ax,0100h        ; return 'done' status
  11782.          ret
  11783.  
  11784.  OutFlush endp
  11785.  
  11786.  DevOpen proc    near            ; function 13 = Device Open
  11787.  
  11788.          mov     ax,0100h        ; return 'done' status
  11789.          ret
  11790.  
  11791.  DevOpen endp
  11792.  
  11793.  DevClose proc   near            ; function 14 = Device Close
  11794.  
  11795.          mov     ax,0100h        ; return 'done' status
  11796.          ret
  11797.  
  11798.  DevClose endp
  11799.  
  11800.  RemMedia proc   near            ; function 15 = Removable Media
  11801.  
  11802.          mov     ax,0100h        ; return 'done' status
  11803.          ret
  11804.  
  11805.  RemMedia endp
  11806.  
  11807.  GenIOCTL proc   near            ; function 16 = Generic IOCTL
  11808.  
  11809.          mov     ax,0100h        ; return 'done' status
  11810.          ret
  11811.  
  11812.  GenIOCTL endp
  11813.  
  11814.  ResetMed proc   near            ; function 17 = Reset Media
  11815.  
  11816.          mov     ax,0100h        ; return 'done' status
  11817.          ret
  11818.  
  11819.  ResetMed endp
  11820.  
  11821.  GetLogDrv proc  near            ; function 18 = Get Logical Drive
  11822.  
  11823.          mov     ax,0100h        ; return 'done' status
  11824.          ret
  11825.  
  11826.  GetLogDrv endp
  11827.  
  11828.  SetLogDrv proc  near            ; function 19 = Set Logical Drive
  11829.  
  11830.          mov     ax,0100h        ; return 'done' status
  11831.          ret
  11832.  
  11833.  SetLogDrv endp
  11834.  
  11835.  DeInstall proc  near            ; function 20 = DeInstall Driver
  11836.  
  11837.          mov     ax,0100h        ; return 'done' status
  11838.          ret
  11839.  
  11840.  DeInstall endp
  11841.  
  11842.  PartFD  proc    near            ; function 22 = Partitionable
  11843.                                  ;               Fixed Disks
  11844.          mov     ax,0100h        ; return 'done' status
  11845.          ret
  11846.  
  11847.  PartFD  endp
  11848.  
  11849.  FDMap   proc    near            ; function 23 = Get Fixed Disk
  11850.                                  ;               Logical Unit Map
  11851.          mov     ax,0100h        ; return 'done' status
  11852.          ret
  11853.  
  11854.  FDMap   endp
  11855.  
  11856.  Error   proc    near            ; bad command code in request header
  11857.  
  11858.          mov     ax,8103h        ; error bit + 'done' status
  11859.                                  ; + "Unknown Command" code
  11860.          ret
  11861.  
  11862.  Error   endp
  11863.  
  11864.  Init    proc    near            ; function 0 = Initialize Driver
  11865.  
  11866.          mov     ax,es:[bx+14]   ; pick up DevHlp entry point
  11867.          mov     word ptr devhlp,ax
  11868.          mov     ax,es:[bx+16]
  11869.          mov     word ptr devhlp+2,ax
  11870.  
  11871.                                  ; set offsets to end of code
  11872.                                  ; and data segments
  11873.          mov     word ptr es:[bx+14],offset _TEXT:Init
  11874.          mov     word ptr es:[bx+16],offset DGROUP:ident
  11875.  
  11876.                                  ; display sign-on message...
  11877.          push    stdout          ; handle for standard output
  11878.          push    ds              ; address of message
  11879.          push    offset DGROUP:ident
  11880.          push    ident_len       ; length of message
  11881.          push    ds              ; receives bytes written
  11882.          push    offset DGROUP:wlen
  11883.          call    DOSWRITE
  11884.  
  11885.          mov     ax,0100h        ; return 'done' status
  11886.          ret
  11887.  
  11888.  Init    endp
  11889.  
  11890.  _TEXT   ends
  11891.  
  11892.          end
  11893.  
  11894.  
  11895.  Figure 9:  Sample Module Definition File
  11896.  
  11897.  ;
  11898.  ; module definition file for
  11899.  ; TEMPLATE OS/2 character device driver
  11900.  ;
  11901.  LIBRARY TEMPLATE
  11902.  PROTMODE
  11903.  
  11904.  
  11905.  Figure 10:  MASM TEMPLATE
  11906.  
  11907.  MASM TEMPLATE;
  11908.  LINK
  11909.  TEMPLATE,TEMPLATE.SYS,,DOSCALLS,TEMPLATE;
  11910.  
  11911.  
  11912.  Summary of the way Device Drivers Queue and Process I/O Requests.
  11913.  
  11914.          ┌─────────────────┐                   ┌────────────────────┐
  11915.          │   OS/2 Kernal   │                   │ Hardware Interrupt │
  11916.          └────────┬────────┘                   └──────────┬─────────┘
  11917.      ┌────────────────────────┐             ┌─────────────────────────┐
  11918.      │ ES:BX ──── I/O Request │             │  OS/2 Interrupt Manager  │
  11919.      └────────────┬────────────┘             └────────────┬─────────────┘
  11920.      ┌────────────────────────┐             ┌─────────────────────────┐
  11921.      │ Device Strategy Routine │             │ Device Interrupt Routine │
  11922.      └────────────┬────────────┘             └────────────┬─────────────┘
  11923.      ┌────────────────────────┐             ┌─────────────────────────┐
  11924.      │Call DevHlp PushReqPacket│             │Service Hardware Interrupt│
  11925.      └──────┬──────────────────┘             └────────────┬─────────────┘
  11926.      ┌──────────────┐   ┌────┐              ┌───────────────────┐
  11927.      │Is Device Idle?├──│ No ├┐             │Is Request Complete │  ┌────┐
  11928.      └──────┬────────┘   └────┘│        ┌────┤      or Error      ├─│ No ├┐
  11929.          ┌────┐               │        │    └────────────────────┘  └────┘│
  11930.          │ Yes │               │     ┌────┐      ┌─────────────────┐      │
  11931.          └──┬──┘               │     │ Yes ├─────│Set Packet Status│      │
  11932.                               │     └─────┘      └───────┬─────────┘      │
  11933.      Call StartNextRequest     │              ┌────────────────────┐      │
  11934.             │─────────────────┘      ┌───────┤ Call DevHlp DevDone │      │
  11935.      ┌──────┴─────────┐               │       └─────────────────────┘      │
  11936.      │   Far Return   │                             ┌────────────────┐    │
  11937.      └────────────────┘   Call StartNextRequest─────│   Far Return   │───┘
  11938.                                                      └────────────────┘
  11939.  
  11940.                                StartNextRequest
  11941.                                       │
  11942.                            ┌──────────────────────┐
  11943.                            │      Call DevHlp      │
  11944.                            │     Pull ReqPacket    │
  11945.                            └──────────┬────────────┘
  11946.                            ┌──────────────────────┐
  11947.                            │    Start Hardware     │
  11948.                            │ Processing of Request │
  11949.                            └──────────┬────────────┘
  11950.                               ┌───────────────┐
  11951.                               │   Far Return   │
  11952.                               └────────────────┘
  11953.  
  11954.  ████████████████████████████████████████████████████████████████████████████
  11955.  
  11956.  Exploring the Structure and Contents of the MS-DOS Object Module Format
  11957.  
  11958.  Richard Wilton
  11959.  
  11960.  A programmer writing a compiler, an assembler, or a language translator
  11961.  must know the details of object module format and processing. To take
  11962.  advantage of LIB and LINK, a language translator must construct object
  11963.  modules that conform to the format and usage conventions specified by
  11964.  Microsoft.
  11965.  
  11966.  Although some MS-DOS language translators generate executable 8086-
  11967.  family machine code directly from source code, most produce object code
  11968.  instead. Typically, a translator processes each file of source code
  11969.  individually and leaves the resulting object module in a separate file
  11970.  bearing an .OBJ extension. The source-code files themselves remain
  11971.  unchanged.
  11972.  
  11973.  After all of a program's source-code modules have been translated, the
  11974.  resulting object modules can be linked into a single executable program.
  11975.  Because object modules often represent only a portion of a complete
  11976.  program, each source-code module usually contains instructions that
  11977.  indicate how its corresponding object code is to be combined with the
  11978.  object code in other object modules when they are linked.
  11979.  
  11980.  The object code contained in each object module consists of a binary image
  11981.  of the program plus program structure information. This object code is
  11982.  not directly executable. The binary image corresponds to the executable
  11983.  code that will ultimately be loaded into memory for execution; it
  11984.  contains both machine code and program data. The program structure
  11985.  information includes descriptions of logical groupings defined in the
  11986.  source code (such as named subroutines or segments) and symbolic
  11987.  references to addresses in other object modules.
  11988.  
  11989.  The program structure information is used by a linkage editor, or
  11990.  linker, such as Microsoft LINK to edit the binary image of the program
  11991.  contained in the object module. The linker combines the binary images
  11992.  from one or more object modules into a complete executable program.
  11993.  
  11994.  The linker's output is an EXE file-a file containing machine code that can
  11995.  be loaded into RAM and executed (see Figure 1). The linker leaves intact
  11996.  all of the object modules it processes.
  11997.  
  11998.  Object code thus serves as an intermediate form for compiled programs.
  11999.  This form offers two major advantages:
  12000.  
  12001.    ■  Modular intermediate code. The use of object modules eliminates the
  12002.       overhead of repeated compilation of an entire program whenever
  12003.       changes are made to parts of its source code. Instead, only those
  12004.       object modules affected by source-code revisions need be
  12005.       recompiled.
  12006.  
  12007.    ■  Shareable format. Object module format is well defined, so object
  12008.       modules can be linked even if they were produced by different
  12009.       translators. Many high-level-language (HLL) compilers take advantage
  12010.       of this commonality of object-code format to support
  12011.       "interlanguage" linkage.
  12012.  
  12013.  
  12014.  Object Module Contents
  12015.  
  12016.  Object modules contain five basic types of information. Some of this
  12017.  information exists explicitly in the source code (and is subsequently
  12018.  passed on to the object module), but much is inferred by the program
  12019.  translator from the structure of the source code and the way memory is
  12020.  accessed by the 8086.
  12021.  
  12022.  Binary image. As described earlier, the binary image comprises executable
  12023.  code (such as opcodes and addresses) and program data. When object
  12024.  modules are linked, the linker builds an executable program from the
  12025.  binary image in each object module it processes. The binary image in each
  12026.  object module is always associated with program structure information
  12027.  that tells the linker how to combine it with related binary images in
  12028.  other object modules.
  12029.  
  12030.  External references. Since an object module generally represents only a
  12031.  small portion of a larger program that will be constructed from several
  12032.  object modules, it usually contains symbols that permit it to be linked
  12033.  to the other modules. Such references to corresponding symbols in
  12034.  other object modules are resolved when the modules are linked.
  12035.  
  12036.  For example, consider the following short C program:
  12037.  
  12038.    main()
  12039.    {
  12040.        puts("Hello, world\n");
  12041.    }
  12042.  
  12043.  This program calls the C function puts() to display a character string,
  12044.  but puts() is not defined in the source code. Rather, the name puts is a
  12045.  reference to a function that is external to the program's main()
  12046.  routine. When the C compiler generates an object module for this program,
  12047.  it will identify puts as an external reference. Later, the linker will
  12048.  resolve the external reference by linking the object module containing the
  12049.  puts() routine with the module containing the main() routine.
  12050.  
  12051.  Address references. When a program is built from a group of object
  12052.  modules, the actual values of many addresses cannot be computed until
  12053.  the linker combines the binary image of executable code and the program
  12054.  data from each of the program's constituent object modules. Object modules
  12055.  contain information that tells the linker how to resolve the values of
  12056.  such addresses, either symbolically (as in the case of external
  12057.  references) or relatively, in terms of some other address (such as the
  12058.  beginning of a block of executable code or program data).
  12059.  
  12060.  Debugging information. An object module can also contain information that
  12061.  relates addresses in the executable program to the corresponding source
  12062.  code. After the linker performs its address fixups, it can use the object
  12063.  module's debugging information to relate a line of source code in a
  12064.  program module to the executable code that corresponds to it.
  12065.  
  12066.  Miscellaneous information. Finally, an object module can contain comments,
  12067.  lists of symbols defined in or referenced by the module, module
  12068.  identification information, and information for use by an object
  12069.  library manager or a linker (for example, the names of object libraries
  12070.  to be searched by default).
  12071.  
  12072.  
  12073.  Terminology
  12074.  
  12075.  When the linker generates an executable program, it organizes the
  12076.  structural components of the program according to the information
  12077.  contained in the object modules. The layout of the executable program can
  12078.  be conceptually described as a run-time memory map after it has been
  12079.  loaded into memory.
  12080.  
  12081.  The basic structure of every executable program for the 8086 family of
  12082.  microprocessors must conform to the segmented architecture of the
  12083.  microprocessor. Thus, the run-time memory map of an executable program
  12084.  is partitioned into segments, each of which can be addressed by using one
  12085.  of the microprocessor's segment registers. This segmented structure of
  12086.  8086-based programs is the basis for most of the following
  12087.  terminology.
  12088.  
  12089.  Frames. The memory address space of the 8086 is conceptually divided into
  12090.  a sequence of paragraph-aligned, overlapping 64Kb regions called frames.
  12091.  Frame 0 in the 8086's address space is the 64Kb of memory starting at
  12092.  physical address 00000H (0000:0000 in segment:offset notation), frame 1 is
  12093.  the 64Kb of memory starting at 00010H (0001:0000), and so forth. A frame
  12094.  number thus denotes the beginning of any paragraph-aligned 64Kb of
  12095.  memory. For example, the location of a 64Kb buffer that starts at address
  12096.  B800:0000 can be specified as frame 0B800H.
  12097.  
  12098.  Logical segments. The run-time memory map for every 8086 program is
  12099.  partitioned into one or more logical segments, which are groupings of
  12100.  logically related portions of the program. Typically, an MS-DOS program
  12101.  includes at least one code segment (that contains all of the program's
  12102.  executable code), one or more data segments (that contain program data),
  12103.  and one stack segment.
  12104.  
  12105.  When a program is loaded into RAM to be executed, each logical segment in
  12106.  the program can be addressed with a frame number──that is, a physical 8086
  12107.  segment address. Before the MS-DOS loader transfers control to a program
  12108.  in memory, it initializes the CS and SS registers with the segment
  12109.  addresses of the program's executable code and stack segments. If an
  12110.  MS-DOS program has a separate logical segment for program data, the
  12111.  program itself usually stores this segment's address in the DS register.
  12112.  
  12113.  Relocatable segments. In MS-DOS programs, most logical segments are
  12114.  relocatable. The loader determines the physical addresses of a program's
  12115.  relocatable segments when it places the program into memory to be
  12116.  executed. However, this address determination poses a problem for the MS-
  12117.  DOS loader, because a program may contain references to the address of a
  12118.  relocatable segment even though the address value is not determined until
  12119.  the program is loaded.
  12120.  
  12121.  The problem is solved by indicating where such references occur within
  12122.  the program's object modules. The linker then extracts this information
  12123.  from the object modules and uses it to build a list of such address
  12124.  references into a segment relocation table in the header of executable
  12125.  files. After the loader copies a program into memory for execution, it
  12126.  uses the segment relocation table to update, or fix up, the segment
  12127.  address references within the program.
  12128.  
  12129.  Consider the example in Figure 2, in which a program loads the starting
  12130.  addresses of two data segments into the DS and ES segment registers.
  12131.  
  12132.  The actual addresses of the _DATA and FAR_DATA segments are unknown when
  12133.  the source code is assembled and the corresponding object module is
  12134.  constructed. The assembler indicates this by including segment fixup
  12135.  information, instead of actual segment addresses, in the program's
  12136.  object module. When the object module is linked, the linker builds this
  12137.  segment fixup information into the segment relocation table in the
  12138.  header of the program's EXE file. Then, when the EXE file is loaded, the
  12139.  MS-DOS loader uses the information in the EXE file's header to patch the
  12140.  actual address values into the program.
  12141.  
  12142.  Absolute segments. Sometimes a program needs to address a predetermined
  12143.  segment of memory. In this case, the program's source code must declare
  12144.  an absolute segment so that a reference to the corresponding frame number
  12145.  can be built into the program's object module.
  12146.  
  12147.  For example, a program might need to address a video display buffer
  12148.  located at a specific physical address. The following assembler
  12149.  directive declares the name of the segment and its frame number:
  12150.  
  12151.    VideoBufferSegSEGMENT at 0B800h
  12152.  
  12153.  Segment alignment. When a program is loaded, the physical address of each
  12154.  logical segment is constrained by the segment's alignment. A segment can
  12155.  be page aligned (aligned on a 256-byte boundary), paragraph aligned
  12156.  (aligned on a 16-byte paragraph boundary), word aligned (aligned on an
  12157.  even-byte boundary), or byte aligned (not aligned on any particular
  12158.  boundary). A specification of each segment's alignment is part of every
  12159.  object module's program structure information.
  12160.  
  12161.  High-level-language translators generally align segments according to
  12162.  the type of data they contain. For example, executable code segments are
  12163.  usually byte aligned; program data segments are usually word aligned. With
  12164.  an assembler, segment alignment can be specified with the SEGMENT
  12165.  directive and the assembler will build this information into the
  12166.  program's object module.
  12167.  
  12168.  Concatenated segments. The linker can concatenate logical segments from
  12169.  different object modules when it builds the executable program. For
  12170.  example, several object modules may each contain part of a program's
  12171.  executable code. When the linker processes these object modules, it can
  12172.  concatenate the executable code from the different object modules into
  12173.  one range of contiguous addresses.
  12174.  
  12175.  The order in which the linker concatenates logical segments in the
  12176.  executable program is determined by the order in which the linker
  12177.  processes its input files and by the program structure information in the
  12178.  object modules. With a high-level-language translator, the translator
  12179.  infers which segments can be concatenated from the structure of the source
  12180.  code and proceeds to build appropriate segment concatenation information
  12181.  into the object modules it generates. With an assembler, the segment class
  12182.  type can be used to indicate which segments can be concatenated.
  12183.  
  12184.  Groups of segments. Segments with different names may also be grouped
  12185.  together by the linker so that they can all be addressed within the same
  12186.  64Kb frame, even though they are not concatenated. For example, it might
  12187.  be desirable to group program data segments and a stack segment within the
  12188.  same 64Kb frame so that program data items and data on the stack can be
  12189.  addressed with the same 8086 segment register.
  12190.  
  12191.  In high-level languages, it is up to the translator to incorporate
  12192.  appropriate segment grouping information into the object modules it
  12193.  generates. With an assembler, groups of segments can be declared with the
  12194.  GROUP directive.
  12195.  
  12196.  Fixups. Sometimes a compiler or an assembler encounters addresses whose
  12197.  values cannot be determined from the source code. The addresses of
  12198.  external symbols are an obvious example. The addresses of relocatable
  12199.  segments and of labels within those segments are another example.
  12200.  
  12201.  A fixup is a language translator's way of passing the buck about such
  12202.  addresses to the linker. Typically, a translator builds a zero value in
  12203.  the binary image at locations where it cannot store an actual address.
  12204.  Accompanying each such location is fixup information, which allows the
  12205.  linker to determine the correct address. The linker then completes the
  12206.  fixup by calculating the correct address value and adding it to the value
  12207.  in the corresponding location in the binary image.
  12208.  
  12209.  The only fixups the linker cannot fully resolve are those that refer to
  12210.  the segment address of a relocatable segment. Such addresses are not known
  12211.  until the program is actually loaded, so the linker, in turn, passes the
  12212.  responsibility to the MS-DOS loader by creating a segment relocation table
  12213.  in the header of the executable file.
  12214.  
  12215.  To process fixups properly, the linker needs three pieces of information:
  12216.  the LOCATION of the value in the object module, the nature of the TARGET
  12217.  (the address whose value is not yet known), and the FRAME in which the
  12218.  address calculations are to take place. Object modules contain the
  12219.  LOCATION, TARGET, and FRAME information the linker uses to calculate
  12220.  the appropriate address for any given fixup.
  12221.  
  12222.  Consider the program in Figure 3. The statement:
  12223.  
  12224.    start:  call    far ptr FarProc
  12225.  
  12226.  contains a reference to an address in the logical segment FarSeg2. Because
  12227.  the assembler does not know the address of FarSeg2, it places fixup
  12228.  information about the address into the object module. The LOCATION to
  12229.  be fixed up is 1 byte past the label start (the 4-byte pointer following
  12230.  the call opcode 9AH). The TARGET is the address referenced in the call
  12231.  instruction-that is, the label FarProc in the segment FarSeg2. The FRAME
  12232.  to which the fixup relates is designated by the group FarGroup and is
  12233.  inferred from the statement
  12234.  
  12235.    ASSUME  cs:FarGroup in the FarSeg2 segment.
  12236.  
  12237.  There are several different ways for a language translator to identify a
  12238.  fixup. For example, the LOCATION might be a single byte, a 16-bit offset,
  12239.  or a 32-bit pointer, as in Figure 3. The TARGET might be a label whose
  12240.  offset is relative either to the base (beginning) of a particular
  12241.  segment or to the actual LOCATION. The FRAME might be a relocatable
  12242.  segment, an absolute segment, or a group of segments.
  12243.  
  12244.  Taken together, all the information in an object module that concerns
  12245.  the alignment and grouping of segments can be regarded as a specification
  12246.  of a program's run-time memory map. In effect, the object module
  12247.  specifies what goes where in memory when a program is loaded. The
  12248.  linker can then take the program structure information in the object
  12249.  modules and generate a file containing an executable program with the
  12250.  corresponding structure.
  12251.  
  12252.  
  12253.   Structure
  12254.  
  12255.  Although object modules contain the information that ultimately
  12256.  determines the structure of an executable program, they bear little
  12257.  structural resemblance to the resulting executable program. Each object
  12258.  module is made up of a sequence of variable-length object records.
  12259.  Different types of object records contain different types of program
  12260.  information.
  12261.  
  12262.  Each object record begins with a 1-byte field that identifies its type.
  12263.  This is followed by a 2-byte field containing the length (in bytes) of the
  12264.  remainder of the record. Next comes the actual structural or program
  12265.  information, represented in one or more fields of varied lengths.
  12266.  Finally, each record ends with a 1-byte checksum.
  12267.  
  12268.  The sequence in which object records appear in an object module is
  12269.  important. Because the records vary in length, each object module must be
  12270.  constructed linearly, from start to end. More important, however, is
  12271.  the fact that some types of object records contain references to
  12272.  preceding object records. Because the linker processes object records
  12273.  sequentially, the position of each object record within an object module
  12274.  depends primarily on the type of information each record contains.
  12275.  
  12276.  
  12277.  Object Record Types
  12278.  
  12279.  Microsoft(R) LINK currently recognizes 14 types of object records, each one
  12280.  of which carries a specific type of information within the object
  12281.  module. Each type of object record is assigned an identifying six-letter
  12282.  abbreviation, but these abbreviations are used only in documentation,
  12283.  not within an object module itself. As already mentioned, the first byte
  12284.  of each object record contains a value that indicates its type. In a
  12285.  hexadecimal dump of the contents of an object module, these identifying
  12286.  bytes identify the start of each object record.
  12287.  
  12288.  Figure 4 lists the types of object records supported by LINK. The value
  12289.  of each record's identifying byte (in hexadecimal) is included, along
  12290.  with the six-letter abbreviation and a brief functional description. The
  12291.  functions of the 14 types of object records fall into six general
  12292.  categories:
  12293.  
  12294.    ■  Binary data (executable code and program data) is contained in the
  12295.       LEDATA and LIDATA records.
  12296.  
  12297.    ■  Address binding and relocation information is contained in FIXUPP
  12298.       records.
  12299.  
  12300.    ■  The structure of the run-time memory map is indicated by the SEGDEF,
  12301.       GRPDEF, COMDEF, and TYPDEF records.
  12302.  
  12303.    ■  Symbol names are declared in LNAMES, EXTDEF, and PUBDEF records.
  12304.  
  12305.    ■  Debugging information is in the LINNUM record.
  12306.  
  12307.    ■  Finally, the structure of the object module itself is determined by
  12308.       the THEADR, COMENT, and MODEND records.
  12309.  
  12310.  
  12311.  Object Record Order
  12312.  
  12313.  The sequence in which the types of object records appear in an object
  12314.  module is fairly flexible in some respects. Several record types are
  12315.  optional, and if the type of information they carry is unnecessary, they
  12316.  are omitted from an object module. In addition, most object record types
  12317.  can occur more than once in the same object module. And, because object
  12318.  records are variable in length, it is often possible to choose, as a
  12319.  matter of convenience, between combining information into one large
  12320.  record or breaking it down into several smaller records of the same
  12321.  type.
  12322.  
  12323.  As stated previously, one important constraint on the order in which
  12324.  object records appear is the need for some types of object records to
  12325.  refer to information contained in other records. Because the linker
  12326.  processes the records sequentially, object records containing such
  12327.  information must precede the records that refer to it. For instance, two
  12328.  types of object records, SEGDEF and GRPDEF, refer to the names contained
  12329.  in an LNAMES record. Thus, an LNAMES record must appear before a SEGDEF
  12330.  or GRPDEF record that refers to it so that the names in the LNAMES record
  12331.  are known to the linker by the time it processes the SEGDEF or GRPDEF
  12332.  records.
  12333.  
  12334.  
  12335.  References Between Object Records
  12336.  
  12337.  Object records can refer to information in other records either
  12338.  indirectly, by means of implicit references, or directly, by means of
  12339.  indexed references to names or other records.
  12340.  
  12341.  Implicit references. Some types of object records implicitly reference
  12342.  another record in the same object module. The most important example of
  12343.  such implicit referencing is in the FIXUPP record, which always contains
  12344.  fixup information for the preceding LEDATA or LIDATA record in the
  12345.  object module. Any time an LEDATA or LIDATA record contains a value that
  12346.  needs to be fixed up, the next record in the object module is always a
  12347.  FIXUPP record containing the actual fixup information.
  12348.  
  12349.  Indexed references to names. An object record that refers to a symbolic
  12350.  name, such as the name of a segment or an external routine, uses an index
  12351.  into a list of names contained in a previous object record. The first name
  12352.  in such a list has the index number 1, the second name has index number 2,
  12353.  the third has index number 3, and so on. Altogether, a list of as many as
  12354.  32,767 (7FFFH) names can be incorporated into an object module-generally
  12355.  adequate for even the most verbose programmer. (LINK does, however,
  12356.  impose its own version-specific limits.)
  12357.  
  12358.  Indexed references to object records. An object record can also refer to a
  12359.  previous object record by using the same type of index. In this case, the
  12360.  index number refers to one of a list of object records of a particular
  12361.  type. For example, a FIXUPP record might refer to a segment by referencing
  12362.  one of several preceding SEGDEF records in the object module. In that
  12363.  case, a value of 1 would indicate the first SEGDEF record in the object
  12364.  module, a value of 2 would indicate the second, and so on.
  12365.  
  12366.  The index-number field in an object record can be either 1 or 2 bytes
  12367.  long. If the number is in the range 0-7FH, the high-order bit (bit 7) is 0
  12368.  and the low-order 7 bits contain the index number, so the field is only 1
  12369.  byte long (see Figure 5).
  12370.  
  12371.  If the index number is in the range 80-7FFFH, the field is 2 bytes long.
  12372.  The high-order bit of the first byte in the field is set to 1, and the
  12373.  high-order byte of the index number (which must be in the range 07FH) fits
  12374.  in the remaining 7 bits. The low-order byte of the index number is
  12375.  specified in the second byte of the field (see Figure 6). The same
  12376.  format is used whether an index refers to a list of names or to a previous
  12377.  object record.
  12378.  
  12379.  
  12380.  Microsoft 8086 Object Record Formats
  12381.  
  12382.  Just as the design of the Intel(R) 8086 microprocessor reflects the design
  12383.  of its 8-bit predecessors, 8086 object record formats are reminiscent
  12384.  of the 8-bit software tradition. In 8-bit systems, disk space and RAM
  12385.  were often at a premium. To minimize the space consumed by object
  12386.  records, information is packed into bit fields within bytes and variable-
  12387.  length fields are frequently used.
  12388.  
  12389.  Microsoft LINK recognizes a major subset of Intel's original 8086 object
  12390.  module specification (Intel Technical Specification 121748-001).
  12391.  Intel also proposed a six-letter name for each type of object record and
  12392.  symbolic names for fields. These names are documented in The MS-DOS
  12393.  Encyclopedia and appear in the order shown earlier in Figure 4.
  12394.  
  12395.  The Intel record types that are not recognized by LINK provide
  12396.  information about an executable program that MS-DOS obtains in other
  12397.  ways. For example, information about run-time overlays is supplied in
  12398.  LINK's command line rather than being encoded in object records. (Because
  12399.  they are ignored by LINK, they are not included in the Encyclopedia.)
  12400.  
  12401.  All 8086 object records conform to the format shown in Figure 7.
  12402.  
  12403.  The record type field is a 1-byte field containing the hexadecimal
  12404.  number that identifies the type of object record (see Figure 4).
  12405.  
  12406.  The record length is a 2-byte field that gives the length of the remainder
  12407.  of the object record in bytes (not including the bytes in the record type
  12408.  and record length fields). The record length is stored with the low-order
  12409.  byte first.
  12410.  
  12411.  The body field of the record varies in size and content, depending on the
  12412.  record type.
  12413.  
  12414.  The checksum is a 1-byte field that contains the negative sum (modulo 256)
  12415.  of all other bytes in the record. In other words, the checksum byte is
  12416.  calculated so that the low-order byte of the sum of all the bytes in the
  12417.  record, including the checksum byte, equals zero.
  12418.  
  12419.  
  12420.  Figure 1:  Generation of an executable (EXE) file
  12421.  
  12422.      ╔═════════════════╗
  12423.      ║      Source     ║
  12424.      ║       Code      ║
  12425.      ╚════════╦════════╝
  12426.     ┌─────────╨─────────┐
  12427.     │Language translator│
  12428.     │  or assembler     │
  12429.     └─────────╥─────────┘
  12430.               
  12431.      ╔═════════════════╗    ┌──────────────┐     ╔═════════════════╗
  12432.      ║  Object module  ║═══╡Object module ╞════║  Object library ║
  12433.      ║   (OBJ file)    ║    │librarian(LIB)│     ║    (LIB file)   ║
  12434.      ╚════════╦════════╝    └──────────────┘     ╚════════╦════════╝
  12435.               ╚════════════════════╦══════════════════════╝
  12436.                               Linker (LINK)
  12437.                                    ║
  12438.                                    
  12439.                           ╔═════════════════╗
  12440.                           ║Executable binary║
  12441.                           ║ image (EXE file)║
  12442.                           ╚════════╦════════╝
  12443.                                MS-DOS loader
  12444.                                    
  12445.                               Program runs
  12446.  
  12447.  
  12448.  Figure 2:  Obtaining Data Segment Starting Addresses
  12449.  
  12450.    mov     ax,seg _DATA
  12451.    mov     ds,ax           ; make _DATA segment
  12452.                            ; addressable through DS
  12453.    mov     ax,seg FAR_DATA
  12454.    mov     es,ax           ; make FAR_DATA segment
  12455.                            ; addressable through ES
  12456.  
  12457.  
  12458.  Figure 3:  A sample "program" containing statements from which the
  12459.             assembler derives fixup information.
  12460.  
  12461.                                  title       fixups
  12462.  
  12463.                     FarGroup     GROUP       FarSeg1, FarSeg2
  12464.  
  12465.  0000               CodeSeg      SEGMENT     byte   public 'CODE'
  12466.                                  ASSUME      cs:CodeSeg
  12467.  
  12468.  0000 9A 0000 -- R  start:       call        far ptr FarProc
  12469.  
  12470.  0005               CodeSeg      ENDS
  12471.  
  12472.  
  12473.  0000               FarSeg1      SEGMENT     byte public   ; part of FarGroup
  12474.  
  12475.  0000               FarSeg1      ENDS
  12476.  
  12477.  0000               FarSeg2      SEGMENT     byte public
  12478.                                  ASSUME      cs:FarGroup
  12479.  
  12480.  0000               FarProc      PROC        far
  12481.  0000 CB                         ret                       ; a FAR return
  12482.  
  12483.                     FarProc      ENDP
  12484.  
  12485.  0001               FarSeg2      ENDS
  12486.  
  12487.                                  END
  12488.  
  12489.  
  12490.  Figure 4:  Types of 8086 Object Records Supported by Microsoft LINK
  12491.  
  12492.  ID byte   Abbreviation   Description
  12493.  
  12494.  80H       THEADR         Translator Header Record
  12495.  88H       COMENT         Comment Record
  12496.  8AH       MODEND         Module End Record
  12497.  8CH       EXTDEF         External Names Definition Record
  12498.  8EH       TYPDEF         Type Definition Record
  12499.  90H       PUBDEF         Public Names Definition Record
  12500.  94H       LINNUM         Line Number Record
  12501.  96H       LNAMES         List of Names Record
  12502.  98H       SEGDEF         Segment Definition Record
  12503.  9AH       GRPDEF         Group Definition Record
  12504.  9CH       FIXUPP         Fixup Record
  12505.  A0H       LEDATA         Logical Enumerated Data Record
  12506.  A2H       LIDATA         Logical Iterated Data Record
  12507.  B0H       COMDEF         Communal Names Definition Record
  12508.  
  12509.  
  12510.  Figure 5:  One-byte Index Number.
  12511.  
  12512.         ┌────────────────────────────────────────────────────┐
  12513.         │                                                    │█
  12514.         │    bit   7    6    5    4    3    2    1    0      │█
  12515.         │        ╔═══╦══════════════════════════════════╗    │█
  12516.         │        ║ 0 ║         Index Number             ║    │█
  12517.         │        ╚═══╩══════════════════════════════════╝    │█
  12518.         │                                                    │█
  12519.         └────────────────────────────────────────────────────┘█
  12520.            ████████████████████████████████████████████████████
  12521.  
  12522.  
  12523.  Figure 6:  One-byte Index Number.
  12524.  
  12525.  bit  7   6   5   4   3   2   1   0   7   6   5   4   3   2   1   0
  12526.      ╔══╦══════════════════════════╦═══════════════════════════════╗
  12527.      ║ 1║     High-Order Byte      ║       Low-Order Byte          ║
  12528.      ╚══╩══════════════════════════╩═══════════════════════════════╝
  12529.                 First byte                   Second byte
  12530.  
  12531.  
  12532.  Figure 7:  8086 Object Record Format
  12533.  
  12534.               ┌───────────────────────────────────────────────┐
  12535.               │                                               │█
  12536.               │    ╔══════╦═════╦═════╦════/═════╦═══════╗    │█
  12537.               │    ║Record║  Record   ║   Body   ║  Chk  ║    │█
  12538.               │    ║ type ║  length   ║          ║  sum  ║    │█
  12539.               │    ╚══════╩═════╩═════╩════/═════╩═══════╝    │█
  12540.               │                                               │█
  12541.               └───────────────────────────────────────────────┘█
  12542.                 ████████████████████████████████████████████████
  12543.  
  12544.  ████████████████████████████████████████████████████████████████████████████
  12545.  
  12546.  A Guide to Program Editors, the Developer's Most Important Tool
  12547.  
  12548.  ───────────────────────────────────────────────────────────────────────────
  12549.  Also see the following related articles:
  12550.    The Brief Editor
  12551.    A BRIEF Journey
  12552.    The ME Text Editor
  12553.    Inside ME
  12554.    A Guide to Developers
  12555.  ───────────────────────────────────────────────────────────────────────────
  12556.  
  12557.  Tony Rizzo
  12558.  
  12559.  The personal computing platform has evolved into a serious and important
  12560.  programming environment. Whenever one thinks of a programming environment,
  12561.  what usually comes to mind is the compiling or assembling of source code
  12562.  and the tools associated with that process, namely compilers and
  12563.  assemblers.
  12564.  
  12565.  However, compilers and assemblers are the very last links in a long chain
  12566.  of events and may represent only a small fraction of a program's
  12567.  development cycle. Throughout this cycle, from concept to design to the
  12568.  actual building of a program, runs a fundamental thread──the evolution of
  12569.  the text that is the source code of a program. What the professional
  12570.  programmer really spends most of his or her time doing is writing and
  12571.  editing the source code by working with a text editor.
  12572.  
  12573.  In the early days of personal computing, many programmers used word
  12574.  processors. WordStar(R), for example, often doubled as a text editor,
  12575.  providing the ability to create a "text" file by stripping any special
  12576.  codes prior to actually writing out the file. It was the first real text
  12577.  manipulation program available for the PC, and there are many
  12578.  programmers who still refuse to consider any other alternative.
  12579.  
  12580.  WordStar continues to influence even the newest compilers as evidenced
  12581.  by the emergence of the integrated editor/compiler environment. These
  12582.  compilers provide a WordStar-compatible (or keystroke-compatible)
  12583.  integrated editor.
  12584.  
  12585.  The PC software business has grown at a tremendous rate, and the
  12586.  "garage/home-brewed" software of yesterday has in many cases given way to
  12587.  sophisticated software teams working in highly competitive and fast-
  12588.  paced environments. A major factor in a programmer's success is
  12589.  productivity.
  12590.  
  12591.  New and faster compilers, assemblers, and debuggers are emerging all the
  12592.  time. Compilers now offer sophisticated code optimization capabilities,
  12593.  and tools such as CodeView(R) provide sophisticated debugging aid. But such
  12594.  tools are of little use without source code. The key to enhancing
  12595.  productivity is the ability to create well-designed source code in the
  12596.  least possible time. Consequently, WordStar no longer meets the
  12597.  strenuous demands of the professional PC programmer.
  12598.  
  12599.  There is a wide variety of editors available to programmers, ranging in
  12600.  price from as little as $40 to more than $300. Some editors are based on
  12601.  mainframe and minicomputer predecessors. Some were specifically
  12602.  developed to operate within the PC environment. Some label themselves as
  12603.  intended strictly for the professional programmer. Some purport to be all
  12604.  things to all people. Whatever their individual heritage or claim to fame
  12605.  might be, there are enough options that a programmer might have
  12606.  difficulty in choosing one editor.
  12607.  
  12608.  The technological advancements of recent generations of PCs have brought
  12609.  us faster, more sophisticated hardware capabilities, particularly in
  12610.  graphics. Each editor offers a unique set of features──multiple windows,
  12611.  EGA support, configurable keyboards, built-in macro languages, and the
  12612.  ability to compile a program using the programmer's preferred compiler or
  12613.  assembler on the fly from within the editor itself.
  12614.  
  12615.  Selecting the "right" editor is a very personal decision. How many times
  12616.  have you heard a fellow programmer complain about his editing tools?
  12617.  Perhaps you've spent most of your professional career working on a UNIX(R)
  12618.  system, only to discover that Emacs and VI don't quite make it in the PC
  12619.  world. Perhaps you only use a mainframe-based editor because you feel safe
  12620.  in knowing how it operates, even though its performance is clearly awful
  12621.  on your AT. Or perhaps you are that WordStar person who'd rather fight
  12622.  than switch.
  12623.  
  12624.  In order to help with your decision, MSJ set out to do a comprehensive
  12625.  review of all the commercially available and advertised program editors.
  12626.  
  12627.  We settled on twelve analysis areas by which to judge each editor:
  12628.  
  12629.    ■  basic editor functions
  12630.    ■  user interface
  12631.    ■  macro capabilities
  12632.    ■  file handling
  12633.    ■  special features
  12634.    ■  support for unique language features
  12635.    ■  print capabilities
  12636.    ■  word processing features
  12637.    ■  documentation
  12638.    ■  setup/installation
  12639.    ■  help/tutorial
  12640.    ■  speed
  12641.  
  12642.  Our first objective was to analyze basic editing and productivity
  12643.  features such as invoking a compiler from within the editor, editing
  12644.  multiple files simultaneously, and multiple windows. Our survey of
  12645.  programmers' requirements also indicated the need for certain word
  12646.  processing features to create quick documentation, write notes, memos,
  12647.  and so on. The ability to wordwrap, justify, and center is also handy.
  12648.  
  12649.  Speed is important, but we weren't as interested in the raw speed
  12650.  required, for example, to go from the top to the bottom of a document as
  12651.  we were in factors such as the number of keystrokes necessary to effect
  12652.  such a move. Often the time spent or wasted in keyboard manipulations
  12653.  plays a much larger role in one's perception of a product's quickness
  12654.  than raw speed. The difference between 2 and 4 seconds is not always as
  12655.  significant as having to use six keystrokes instead of two to do
  12656.  something. Nevertheless, for those of you who value such measurements we
  12657.  have included two. These speed measurements were all recorded on an
  12658.  unmodified 8-MHz IBM(R) PC/AT(R) (model 339).
  12659.  
  12660.  The chart accompanying the reviews lists the capabilities of all twelve
  12661.  editors. We have listed the editors in order from highest rating to
  12662.  lowest. The editors that received the three highest ratings were chosen
  12663.  for more detailed review.
  12664.  
  12665.  In addition, we offered the developers the opportunity to comment on
  12666.  some of the underlying design and technology issues they had to deal
  12667.  with in order to create their editors. We also encouraged them to give us
  12668.  a glimpse into their plans for the future.
  12669.  
  12670.  What we were looking for in this search-and-discover mission was proof
  12671.  that text editors have been keeping pace with other, more heavily advertised
  12672.  systems tools. We think our results point to the best-engineered editors now
  12673.  available on the market.
  12674.  
  12675.  Editing tools may not seem as important to obtain as the latest and
  12676.  greatest optimizing compiler. However, given the capabilities of this
  12677.  new generation of editors, we strongly suggest that you seriously
  12678.  reevaluate your current text editor. See how it stacks up against the best
  12679.  editors now on the market. You may just discover that a big boost to your
  12680.  productivity, as well as to your competitiveness as a programmer, awaits
  12681.  you.
  12682.  
  12683.  Microsoft Systems Journal believes strongly in the success of well-
  12684.  engineered products, whether they are Microsoft's or another vendor's. The
  12685.  three text editors that received the highest ratings are highly
  12686.  recommended──they clearly deserve your attention.
  12687.  
  12688.  There was one big surprise among the top three editors. Magma Systems' ME
  12689.  retails for all of $39.95. What can one say about such a discovery? The
  12690.  reviewer has considerable programming experience and he was very impressed,
  12691.  enough to consider it superior to other editors costing many times its
  12692.  price.
  12693.  
  12694.  Microsoft feels that OS/2 is going to become the operating system of
  12695.  choice for the majority of PCs over the next several years and is the
  12696.  ideal platform for the development of the next generation of applications.
  12697.  Is there an editor out there that will not only keep you competitive today
  12698.  but also in the protected-mode operating system world of tomorrow? Read on
  12699.  and find out.
  12700.  
  12701.  
  12702.  ───────────────────────────────────────────────────────────────────────────
  12703.  The BRIEF Editor
  12704.  ───────────────────────────────────────────────────────────────────────────
  12705.  
  12706.  Matt Trask
  12707.  
  12708.  BRIEF(R), an acronym for "Basic Reconfigurable Interactive Editing
  12709.  Facility," is a full-featured programmer's editor for DOS-based personal
  12710.  computers. Marketed by Solution Systems(R), it is moderately priced at
  12711.  $195. Even though it is not my usual editor, BRIEF is intuitive enough
  12712.  and the on-line help is good enough that I was able to use it to write
  12713.  this review.
  12714.  
  12715.  
  12716.  Documentation
  12717.  
  12718.  The documentation consists of two wire-bound paperback manuals and a quick
  12719.  reference booklet, enclosed in the typical IBM-style slip case so that
  12720.  they can maintain their own shelf space. The User's Guide includes a
  12721.  tutorial, an alphabetical reference to the editor's commands, and an
  12722.  index. In addition there is a complete summary of messages that BRIEF can
  12723.  generate and a chapter with some common questions and answers about
  12724.  editor operation.
  12725.  
  12726.  The Macro Language Guide is a complete tutorial and reference for
  12727.  BRIEF's built-in macro capability. As in any language reference there is
  12728.  a section that defines the syntax and structure of the language. Two
  12729.  examples of real-world macros are given-a searching package and a restore
  12730.  function. The section on the restore function is an informative and
  12731.  sometimes humorous visit with one of the editor's authors, Dave Nanian,
  12732.  as he demonstrates the process of developing a macro that can restore the
  12733.  editor's state (such as open files, search pattern, search direction, and
  12734.  windows) on startup to the point where it was previously exited. This
  12735.  section is an excellent insight into program development methods for any
  12736.  new programmer as Nanian goes through the whole cycle (idea, fleshing it
  12737.  out, pseudo-code, detail work, getting stuck, maintenance) in the example.
  12738.  
  12739.  The documentation includes a card that summarizes the side effects of the
  12740.  installation procedure so that you will have an idea of what the setup
  12741.  program will or will not do to your AUTOEXEC.BAT before you run it. The
  12742.  quick reference booklet is somewhat superfluous as it is easier to use
  12743.  the on-line help than to find and page through a printed reference.
  12744.  
  12745.  Although the tutorial provided with BRIEF is not of the flashy on-line
  12746.  computer-assisted-instruction type, it is thorough and more than adequate
  12747.  to get a new user up and running in a short amount of time. The first
  12748.  forty-odd pages of the User Guide are an interactive quick-start lesson
  12749.  that goes through the few editor features needed for most purposes. This
  12750.  tutorial covers startup, shutdown, editing, cursor motion, help, undo,
  12751.  cutting, pasting, windows, and other editing functions. At the end of
  12752.  this section there is a quiz to test your comprehension of the material
  12753.  by solving a typical editing problem. The next five chapters cover BRIEF
  12754.  in greater depth, getting into theory of operation and more advanced
  12755.  functionality.
  12756.  
  12757.  
  12758.  Setup and Installation
  12759.  
  12760.  BRIEF comes with a program called SETUP.EXE that configures many of the
  12761.  editor's operating parameters. You need to run this program once before
  12762.  using the editor, but the default values are adequate for getting started.
  12763.  SETUP can be run again later to reconfigure the editor as you become more
  12764.  familiar with the available features and wish to tune them to your
  12765.  preferences.
  12766.  
  12767.  CONFIG.SY and AUTOEXEC.BAT are modified by the setup program to add
  12768.  device drivers, if needed, and set environment variables that inform the
  12769.  editor of your choices. The documentation for the AUTOEXEC changes is
  12770.  adequate in case you wish to change these by hand instead of using
  12771.  SETUP.EXE. In addition, the setup program generates and compiles a
  12772.  startup macro that contains initialization commands for screen
  12773.  colors, autosave, and other options that can be customized. You can edit
  12774.  and recompile this macro to add any other actions you desire at startup.
  12775.  
  12776.  Among the configurable parameters are display parameters (color
  12777.  preferences, cursor shape, use of 43-line mode on EGA, snow test on CGA
  12778.  adapters, and video page test), filename extensions (compiler control
  12779.  commands, word processing features and autoindentation, including
  12780.  normal, smart, and template), and safety features (enable autosave, set
  12781.  autosave timeout, use of backup files, depth of undo stack, and enable
  12782.  session save/restore). Other parameters include initial insert/overstrike
  12783.  mode, empty spaces filled with tabs or spaces, whether to use Ctrl-Z at
  12784.  end of file, keyboard autorepeat rate, default line length, case
  12785.  sensitivity during searches, and use of regular expressions in
  12786.  searches.
  12787.  
  12788.  Context-sensitive help is available during setup. The setup help has a
  12789.  message, "Press Alt-H if you need more assistance," that appears and
  12790.  flashes intermittently after a period of inactivity on the keyboard. This
  12791.  attention to detail in the user interface is indicative of the
  12792.  thoroughness of the product.
  12793.  
  12794.  The only complaints I have with the setup procedure are the assumptions
  12795.  about drives and directories. SETUP asks which disk is to be used and then
  12796.  creates a subdirectory called \BRIEF, rather than asking where the
  12797.  editor's files should be kept. It assumes that the target drive is also
  12798.  the boot drive and therefore creates and modifies AUTOEXEC.BAT and
  12799.  CONFIG.SYS in the root directory of this drive rather than finding out
  12800.  where the real copies of these files are.
  12801.  
  12802.  
  12803.  Help
  12804.  
  12805.  The context-sensitive pop-up help system is excellent. Pressing Alt-H
  12806.  brings up a help menu during text entry or editing. This menu is the
  12807.  entry into a hierarchical help window subsystem that covers such topics
  12808.  as blocks, keys and commands, and windows. After selecting one of the
  12809.  menu choices, another press of Alt-H shows which key or key combination
  12810.  performs that editor function.
  12811.  
  12812.  If you are currently in the middle of an editor function such as Alt-E
  12813.  (open and edit a new file), Alt-H goes directly to the help window that
  12814.  describes that function. There is also an on-line quick reference
  12815.  available from the help menu that lists all available editor functions
  12816.  and their current key assignments. Another menu selection accepts a
  12817.  keystroke and tells you what function is performed by that key.
  12818.  
  12819.  
  12820.  Basic Editor Functions
  12821.  
  12822.  BRIEF does almost everything imaginable in the areas of cursor motion,
  12823.  block operations, delete, copy, search-and-replace, and so on, and does
  12824.  them quite well. One powerful feature not usually found in PC editors is
  12825.  the availability of UNIX-style regular expressions for searching, in
  12826.  addition to simple string matching. For instance, the angle bracket in a
  12827.  search for "<if" causes BRIEF to find all lines beginning with "if," and a
  12828.  search for "{[Hh]i} world" will find "hi world" and "Hi world."
  12829.  
  12830.  The ability to adjust the keyboard repeat rate at setup time affects the
  12831.  cursor motion. I made the mistake of setting this at the maximum repeat
  12832.  rate for efficiency, only to watch as a repeated backspace to correct an
  12833.  error about 10 characters to the left gobbled up all the text on the line.
  12834.  I was rescued by the undo (Alt-U) capability, which is configurable as a
  12835.  stack that saves editor actions, not just deleted text as some editors do.
  12836.  This is usually set up to save the 30 most recent actions, but you can
  12837.  configure it to your liking.
  12838.  
  12839.  Word processing is not a strong point of BRIEF, but it is certainly
  12840.  adequate for writing documentation and memos. A macro package is provided
  12841.  that can be attached to a file extension such as .DOC or .TXT and
  12842.  automatically enable the available word processing features when a file
  12843.  with this extension is edited. BRIEF's features include wordwrap, margins,
  12844.  centering, and automatic paragraph reformat. If you want more, you can
  12845.  enhance the supplied source code to the word processing macros. A truly
  12846.  ambitious programmer could conceivably write a full-featured word
  12847.  processor by using nothing more than BRIEF's macro language.
  12848.  
  12849.  
  12850.  User Interface
  12851.  
  12852.  The user interface in BRIEF is more visual than other, more terminal-
  12853.  oriented editors. It takes advantage of special PC features such as color
  12854.  and line drawing characters that are usually not available in non-PC
  12855.  environments.
  12856.  
  12857.  Since every key on the keyboard is reconfigurable, you can assign the
  12858.  editing functions such as search or delete in any way that you please.
  12859.  Macros can be assigned to individual keys or can be executed by the
  12860.  Execute Command key (F10 in the default configuration). This lets you
  12861.  configure BRIEF to look like another editor more to your liking, for
  12862.  instance PC/Vi or WordStar. A package that configures BRIEF to PC/Vi's
  12863.  user interface is available from the authors. The remember and playback
  12864.  functions make it easy to record keystrokes on the fly for reuse if you
  12865.  need to repeat a sequence of actions.
  12866.  
  12867.  Screen layout is one area in which personal preference often borders on
  12868.  fanaticism. BRIEF has all the things that I like on the screen, no more
  12869.  and no less. Besides the name of the current document in the top border,
  12870.  the status line at the bottom of the screen shows the current line and
  12871.  column occupied by the cursor and the time of day. If you don't like a
  12872.  border around the screen, you can disable it at setup time. You can modify
  12873.  foreground, background, and message colors with SETUP to support whatever
  12874.  is available on your system.
  12875.  
  12876.  BRIEF uses tiled windows instead of the overlapping Macintosh-style
  12877.  windows. I generally prefer the latter, but in an editing context, a
  12878.  window is usually used when more than one file has to be visible, so this
  12879.  is not really a limitation. A 43-line display mode is available when
  12880.  running on an EGA but the tiny characters are a little tough on the eyes.
  12881.  This large screen mode would be useful if you are in an extended editing
  12882.  session that requires the use of windows, since you can make each window
  12883.  larger or have more windows.
  12884.  
  12885.  The cursor shape changes to indicate which mode (insert or overstrike) is
  12886.  currently in effect. The space past the end of the last character on a
  12887.  line is referred to as virtual space──the cursor is allowed to move there
  12888.  but it changes shape to indicate that it is in virtual space rather than
  12889.  on top of a space character.
  12890.  
  12891.  Nonstandard enhanced displays such as the WYSE(TM) 700 and Hercules(R)
  12892.  Graphics Plus are supported at their maximum resolution by device
  12893.  drivers that are installed by SETUP.
  12894.  
  12895.  
  12896.  Unique Language Features
  12897.  
  12898.  BRIEF supports autoindenting and delimiter checking for all languages.
  12899.  The C language support is quite extensive. "Smart" indenting uses a
  12900.  knowledge of the language syntax to automatically indent and outdent
  12901.  as control structures open and close. Template indenting provides for
  12902.  automatic completion of C language keywords. The keyword completion
  12903.  can be confusing; it is probably useful for a programmer who only uses
  12904.  C.
  12905.  
  12906.  
  12907.  Macros
  12908.  
  12909.  BRIEF is truly outstanding in its macro capabilities. The built-in LISP-
  12910.  like macro language is a complete general-purpose programming language.
  12911.  It covers all the features that you would expect in a language except port
  12912.  I/O and system calls. Even this is not too much of a limitation; if you
  12913.  want to write a program such as a word processor with BRIEF macros, the
  12914.  built-in "dos" macro can execute external programs that perform these
  12915.  operations. The exit codes from such programs are retrieved by BRIEF and
  12916.  are returned to the calling macro. If the external program needs to
  12917.  return data, a file can be used for communication.
  12918.  
  12919.  Rudimentary timer functionality is performed by registering a macro for
  12920.  idle time processing when the keyboard is inactive. This mechanism
  12921.  operates BRIEF's autosave feature whenever there is a period of
  12922.  keyboard inactivity. The amount of time delay before autosave starts is
  12923.  a setup option.
  12924.  
  12925.  Two sets of primitives are provided: language primitives such as if,
  12926.  while, get_parm, and editing primitives such as inq_position for cursor
  12927.  control and del_edge for window modification. These primitives are the
  12928.  equivalent of the function library that is provided with most language
  12929.  translators for linkage with your own programs. Some of the available
  12930.  functions are atoi (convert an ASCII number in a string to an integer),
  12931.  beep (sound the PC's speaker), color (set the foreground, background, and
  12932.  message colors), compress (shorten multiple white spaces to single ones),
  12933.  create_window (create an overlapping window such as that which the help
  12934.  subsystem uses), keyboard_flush (discard any pending keystrokes),
  12935.  remember (record a sequence of keystrokes to playback later), and
  12936.  write_buffer (write the current file out to disk).
  12937.  
  12938.  
  12939.  File Handling
  12940.  
  12941.  Maximum file size is limited only by the available disk space. BRIEF
  12942.  automatically pages a file to disk as needed to free up memory for
  12943.  editing. Inserting the contents of one file into another by using the
  12944.  multiple file editing commands and the block copy command is
  12945.  straightforward.
  12946.  
  12947.  
  12948.  Special Features
  12949.  
  12950.  BRIEF supports an extensive list of translators for operation within the
  12951.  editor, which includes automatic return to the source file on error with
  12952.  the cursor positioned at the cause of the error. Some of the supported
  12953.  translators are MASM, Lattice and Microsoft(R) C, dBASE(R), R-M COBOL, Lahey
  12954.  FORTRAN, Microsoft QuickBASIC, and Oregon Pascal-2. Many other compilers
  12955.  are supported, and examples show you how to integrate a compiler that is
  12956.  not supported into this environment.
  12957.  
  12958.  Multiple files can be opened at the same time with the Alt-B buffer
  12959.  command, which pops up a list of all currently open files for direct
  12960.  selection. Some or all of these files can be displayed in tiled windows
  12961.  simultaneously. I don't find windows to be particularly helpful on a
  12962.  system with a small screen such as an EGA. The Macintosh paradigm is only
  12963.  useful in that it models the way work is done by showing a pile of windows
  12964.  (tasks) currently available, but the current task is given a full-sized
  12965.  screen (or close to it) to work on. If you split an 80-column screen into
  12966.  left and right halves, enough work, such as code comments, is obscured
  12967.  that it is nearly useless. On the other hand, if you use BRIEF with a WYSE
  12968.  700 display that supports 50-lines-by-160-columns, four full screens can
  12969.  be shown at a time.
  12970.  
  12971.  BRIEF has two features of great value: a support conference on the BIX
  12972.  network and autosave. The conference gives a user direct access to the
  12973.  program's authors, an excellent supplement to the staff support offer by
  12974.  Solution Systems, as well as a forum for macro exchange. The autosave
  12975.  feature writes a copy of the current file out to disk with the extension
  12976.  .ASV whenever the keyboard is idle and deletes this file on a normal exit
  12977.  to DOS. If there is a power failure (or you're working at home and your
  12978.  two year old discovers the Reset button), this file enables you to recover
  12979.  with no lost data.
  12980.  
  12981.  
  12982.  Speed
  12983.  
  12984.  I am accustomed to using 286-and 386-based systems where program
  12985.  performance is usually not a concern. One editor that I customarily use
  12986.  practically requires these faster systems to be useful. I installed BRIEF
  12987.  on an XT-type system to see if this was the case and found no discernible
  12988.  difference. Of course, major compute-intensive operations such as a
  12989.  large search-and-translate benefit from a faster CPU, as is true of most
  12990.  programs.
  12991.  
  12992.  The responsiveness of the keyboard and other parts of the user interface
  12993.  is crisp, with no noticeable delays.
  12994.  
  12995.  
  12996.  Print Capabilities
  12997.  
  12998.  Other than the ability to mark and print a block, there is no special
  12999.  support for printing in BRIEF. You can set right margins and use embedded
  13000.  formfeed characters for pagination. However, printer features such as
  13001.  fonts are not supported.
  13002.  
  13003.  
  13004.  Vendor Support
  13005.  
  13006.  Vendor support is very good. I had a lot of trouble getting through the
  13007.  initial installation of BRIEF due to a bug in another product that was
  13008.  installed by my AUTOEXEC.BAT. After a short description of the symptoms,
  13009.  the very sharp support person said, "Oh, you must be running xxx.COM; it
  13010.  has a bug in the use of the SI register."
  13011.  
  13012.  Instead of threatening you with imprisonment in return for no guarantee of
  13013.  product usefulness, the license agreement restricts you to using BRIEF
  13014.  on only one keyboard at a time. Solution Systems also offers a service in
  13015.  which you can have your name embedded into the code along with the serial
  13016.  number so that you can keep track of who is using your program.
  13017.  
  13018.  An OS/2 version of BRIEF is currently under development for release in
  13019.  early 1988, and BRIEF's author tells me that the current UNIX version is
  13020.  being ported to other popular UNIX environments that support Sys V.3.
  13021.  
  13022.  
  13023.  Conclusions
  13024.  
  13025.  There are a lot of things to like about BRIEF. It takes advantage of
  13026.  available PC features such as 43-line mode on the EGA. Multiple windows
  13027.  make it easy to keep a spec in view while writing new source code.
  13028.  Compilation within the editor with automatic return of the cursor to the
  13029.  error can save a lot of time waiting for printouts of compile-time
  13030.  errors. One power failure is all that it takes to appreciate autosave-the
  13031.  value is not in the saving of typing time, but in the time saved not
  13032.  having to reconstruct the thought processes behind your most recent
  13033.  changes.
  13034.  
  13035.  The one feature that makes BRIEF worth the effort of learning to use a new
  13036.  editor is its programmability. The restore macro is probably the best
  13037.  advance in productivity since editors that can operate on multiple files.
  13038.  The ability to return to your PC and invoke BRIEF such that it restores
  13039.  all opened files, cursor positions, and search strings allows you to
  13040.  casually walk away without worrying about the time it would take to
  13041.  restore your mental state when it is time to start again. If Solution
  13042.  Systems would just modify the restore macro to save multiple sessions for
  13043.  different concurrent projects, an excellent program would be even better.
  13044.  
  13045.  
  13046.  ───────────────────────────────────────────────────────────────────────────
  13047.  A BRIEF Journey
  13048.  ───────────────────────────────────────────────────────────────────────────
  13049.  
  13050.  David Nanian and Michael Strickman
  13051.  
  13052.  From the start, we envisioned that BRIEF would need to run under a number
  13053.  of different operating systems. To make this task easier, and because it's
  13054.  good programming practice, BRIEF was designed to be as modular as
  13055.  possible. We made logical divisions along functional boundaries,
  13056.  splitting the main program into a number of independently operating
  13057.  subsystems that communicated through standard function interfaces.
  13058.  While many of the data structures are shared between modules, all of the
  13059.  substructures that are related to a particular module are handled by
  13060.  that module alone.
  13061.  
  13062.  The basic modules, or subsystems include: virtual memory, windows,
  13063.  logical display (refresh), physical device, input handling, macro
  13064.  interpretation, undo, buffer modification, and command dispatch.
  13065.  
  13066.  Each of these subsystems can be replaced with functions tailored to a
  13067.  particular target system. The only requirement is that the new code must
  13068.  adhere to the original subsystem interface. In the case of operating
  13069.  system subsystem replacement, this usually means including some
  13070.  intermediate "glue" routines.
  13071.  
  13072.  Most of BRIEF's subsystems were designed to act as independently as
  13073.  possible. For example, the part of BRIEF that handles changes to files
  13074.  doesn't know anything about windows, nor does it know how the file it is
  13075.  manipulating is going to be displayed. It works purely at a stream level,
  13076.  and expects that other subsystems will provide the file's stream data and
  13077.  present the results to the user.
  13078.  
  13079.  Similarly, the refreshing subsystem doesn't really know anything about
  13080.  the physical representation of the windows that it's refreshing since
  13081.  it's completely device-independent. The subsystem only assumes that it
  13082.  is refreshing ASCII text (from the file subsystem) onto some sort of
  13083.  rectangular area that can be addressed in x,y coordinates through the
  13084.  physical device interface. The algorithm redisplays the minimum amount
  13085.  of information necessary; at the physical device level these operations
  13086.  might translate to bit BLTs on a graphics display, block moves on a
  13087.  memory-mapped CRT, or scrolling operations on a terminal.
  13088.  
  13089.  Substantial portions of the editor have been implemented in BRIEF's macro
  13090.  language. We chose the macro language over either C or Assembler for
  13091.  several reasons.
  13092.  
  13093.  First, the macros themselves are virtual, both in size and in their
  13094.  interface to BRIEF. Virtual size means that the virtual memory subsystem
  13095.  reads portions of the macro as they're needed, so the entire macro does
  13096.  not have to be in memory at one time. The virtual interface means that
  13097.  the macros communicate with a "BRIEF" machine, rather than with any
  13098.  particular operating system or hardware, so they can be ported without
  13099.  change from environment to environment.
  13100.  
  13101.  More importantly, BRIEF was designed to let the user completely customize
  13102.  the editor. We could have made the source code for the entire package
  13103.  available. Although this option would allow the most complete
  13104.  reconfiguration of the program──essentially, reconfiguration without
  13105.  limits──it also creates many problems. Configuration requires extensive
  13106.  knowledge of BRIEF's internals, making "simple" configuration all but
  13107.  impossible; technical support is more difficult; updating the package
  13108.  is extremely time-consuming; moving the configured system to other
  13109.  environments is almost impossible; and, our secrets would be available
  13110.  to all of our competitors. Clearly, this was not the way to go.
  13111.  
  13112.  Another option was to provide users with a standard "library" of editing
  13113.  functions which would, in essence, be the BRIEF subsystems. A library
  13114.  could be provided through a .LIB file, a .DLL under OS/2, or even a run-
  13115.  time interface. While a library would have made it easier to update
  13116.  users, the other problems still remained.
  13117.  
  13118.  We decided to take our virtualization philosophy a step further. Rather
  13119.  than forcing the user to deal with the specifics of the system BRIEF was
  13120.  running on, we provided the user with a set of "virtual" interfaces that
  13121.  allow complete configuration without worrying about the environment or
  13122.  specific, low-level implementation details. By writing a large portion of
  13123.  BRIEF in the macro language, we provide an opportunity for users to learn
  13124.  from the coding that we've done, and to change that code to match their
  13125.  own style.
  13126.  
  13127.  BRIEF's modular design makes it easier for us to port BRIEF to other
  13128.  environments. For example, we are currently porting BRIEF to OS/2. The
  13129.  BRIEF port consists mainly of changes allowing it to conform to the
  13130.  virtual memory capabilities of the operating system. Refreshing code,
  13131.  window management, macros, and high-level input remain largely unchanged.
  13132.  The facilities provided by BRIEF's device drivers come primarily from
  13133.  the operating system, and hence that system-specific code has been
  13134.  removed. Much of the OS/2 version of BRIEF is substantially less
  13135.  complicated than the one that runs under DOS.
  13136.  
  13137.  The only problem with the OS/2 port so far has been our dependence on the
  13138.  small programming model and the DOS memory allocator. BRIEF makes
  13139.  extensive use of DOS memory allocation blocks, and we have stayed with
  13140.  the small model (with some far routines) to get the most out of PC and XT
  13141.  computers. Under OS/2, memory allocation has changed and we can no
  13142.  longer assume that allocated blocks returned from the operating system are
  13143.  paragraph-aligned segments (which, under DOS, we could store as a 16-bit
  13144.  pointer with an implied 0 offset). Although a similar scheme could have
  13145.  been implemented under OS/2, the overhead and large number of selectors
  13146.  required made it impractical. We have converted BRIEF to a large model
  13147.  program, and a lot of time has been spent converting various pointer
  13148.  references in C and assembly language.
  13149.  
  13150.  Overall, the port to OS/2 has been greatly assisted by BRIEF's modular
  13151.  design. We anticipate that porting the program to environments such as
  13152.  the Presentation Manager, UNIX(R) (and possibly even the Macintosh(TM)) will
  13153.  be easier because of this design.
  13154.  
  13155.  ───────────────────────────────────────────────────────────────────────────
  13156.  The ME Text Editor
  13157.  ───────────────────────────────────────────────────────────────────────────
  13158.  
  13159.  Greg Comeau
  13160.  
  13161.  Editing text is an important part of program development and maintenance.
  13162.  Like most other programmers, a good portion of my time is spent using a
  13163.  text editor. The editor of choice must be powerful, customizable,
  13164.  extensible, reliable, and of course fast.
  13165.  
  13166.  The Magma Editor Version 1.31, or ME as Magma Systems calls it, stands out
  13167.  among the text editors I looked at. It supports editing of multiple
  13168.  files, full and split-screen windows (pop-up or pull-down), 43-line EGA
  13169.  mode, regular expression searches, cut-and-paste block commands, a C-like
  13170.  macro language, compiles from within the editor returning with the cursor
  13171.  on any offending lines of source, and more. Just before deadline, I
  13172.  received the latest version of ME, Version 2.0 (which as of this writing
  13173.  is in beta testing), and it's loaded with new features.
  13174.  
  13175.  ME comes with help files, a macro compiler, sample macros, and a utility
  13176.  called CTAGS in addition to the editor itself. Installation was as simple
  13177.  as inserting the distribution floppy and typing INSTALL. Magma puts
  13178.  comments in the installation script so that you can modify it in case
  13179.  you don't want to install the whole package.
  13180.  
  13181.  
  13182.  Documentation
  13183.  
  13184.  The documentation for ME is not outstanding. Most commercial software
  13185.  comes in a sturdy box containing high-quality documentation, usually in
  13186.  a slip-case binder. ME arrived on a disk packaged in a disk mailer, which
  13187.  is not exactly an overwhelming presentation.
  13188.  
  13189.  After installation, I found out that you have to print your own copy of a
  13190.  file named MANUAL if you want a hard copy of the documentation. The manual
  13191.  is very disorganized and has no index, and the command reference is
  13192.  located in a file called KEYCHART rather than in the manual itself. It was
  13193.  very hard to find sections describing certain features.
  13194.  
  13195.  However, the manual is complete, and all features of ME are described at
  13196.  least once. Examples of all commands and functions are provided; more
  13197.  complicated features had more than one example. Perhaps the best feature
  13198.  of ME's documentation is that it makes it perfectly clear from the start
  13199.  how to exit from the editor, something that some editors don't do.
  13200.  
  13201.  Although I haven't seen it, Magma Systems promises that Version 2.0 will
  13202.  have a new, bound and typeset manual.
  13203.  
  13204.  
  13205.  On-line Help
  13206.  
  13207.  Once ME is installed, you invoke it by typing ME followed by the filename.
  13208.  Then, once the program is running, you can go into an on-line help
  13209.  subsystem that is not very elaborate but does the job. The text for the
  13210.  on-line help is on disk at all times, and you can change it to suit your
  13211.  needs. Changing the text to the help system is as simple as editing or
  13212.  creating a new text file by using ME and letting ME know where the file
  13213.  is located at startup, usually in \ME\HELP. Version 2.0 will have an
  13214.  expanded help subsystem that is more readable and easier to use.
  13215.  
  13216.  The general behavior of ME is consistent and reliable. I found no
  13217.  surprises and felt comfortable with it. The Ctrl-key combinations are
  13218.  set up logically and if you don't like them they can be changed. Major
  13219.  features such as buffering, windowing, and block operations are
  13220.  complete and operate without hesitation.
  13221.  
  13222.  
  13223.  Buffers and Windows
  13224.  
  13225.  Much of ME's functionality is based on buffer and window operations. A
  13226.  buffer is an area of memory that holds data; in an editor, buffers are
  13227.  responsible for holding varying lines of text and are important tools for
  13228.  a programmer's productivity. ME supports two different types of buffers;
  13229.  the first is called a scratch (or pick) buffer, which is useful for moving
  13230.  text on the screen from one area to another or between windows. ME can
  13231.  handle up to ten scratch buffers at a time. So, for instance, you could
  13232.  delete or mark a few lines from one part of the program you are editing
  13233.  and place them somewhere else in the file. Another possibility is to add
  13234.  the same group of lines to many files, say a copyright message or some
  13235.  variable declarations, by loading each file, moving to the appropriate
  13236.  place, and inserting the scratch buffer.
  13237.  
  13238.  The second type of buffer can have files loaded into it and can be seen in
  13239.  a window, a view into a buffer displayed on your screen. Each window is
  13240.  limited to a certain number of lines ranging from 1 to 24, so you can have
  13241.  up to 24 windows active at any one time. (You can have an unlimited
  13242.  number of buffers if you use ME's macro language described below.) Since
  13243.  things can get cluttered, ME allows windows to be exploded so as to fill
  13244.  the whole screen. Other windows displayed at the time of the explosion do
  13245.  not go away; they become temporarily invisible until you shrink the
  13246.  window currently in use.
  13247.  
  13248.  ME typically loads with only one window displaying the source file that
  13249.  was given as its command-line argument. You can add extra windows by
  13250.  specifying more files on the command line or by creating new windows
  13251.  after a session has started with the split-window command. Once multiple
  13252.  windows have been established, you can delete a window, increase or
  13253.  decrease the number of lines, choose between or explode windows, and
  13254.  change the color of windows.
  13255.  
  13256.  Version 2.0 of ME will have pop-up windows for adding messages to a
  13257.  customized user interface and vertical windows for comparing source
  13258.  files.
  13259.  
  13260.  
  13261.  Macros
  13262.  
  13263.  ME supports various flavors of macros, the simplest of which is the
  13264.  keyboard macro. This macro allows you to record all your keystrokes so
  13265.  that you don't have to worry about typing them over and over if you use
  13266.  them repeatedly.
  13267.  
  13268.  Keyboard macros can also be transformed into another type of macro,
  13269.  called a named macro, if you find that you must abort the current edit
  13270.  session or find yourself creating the same keyboard macro over and over.
  13271.  Named macros allow you to create commands that the ME user interface does
  13272.  not provide directly. For instance, ME can only display 24 windows at a
  13273.  time; since there are 10 scratch buffers and one buffer per window, the
  13274.  ME interface permits a maximum of 34 buffers. Using a macro of your own
  13275.  creation you could add as many buffers as you'd like. For instance,
  13276.  
  13277.    mymacro()
  13278.      {
  13279.      int   bufid;
  13280.      bufid = create_buffer("tempbuf");
  13281.      show_buffer(bufid);
  13282.             ∙
  13283.             ∙
  13284.             ∙
  13285.      close_window();
  13286.      delete_buffer(bufid);
  13287.      }
  13288.  
  13289.  creates a buffer named "tempbuf" and attaches it to a new window. It can
  13290.  then have code to process and perhaps save the data in that window; at
  13291.  some point it must close the window and delete the buffer.
  13292.  
  13293.  An excellent example of a macro is shown in Figure 1. It contains a sample
  13294.  ME macro script to compile Microsoft C Version 4.0 programs and report any
  13295.  errors found. Notice its C-like syntax and its use of variables. The
  13296.  example uses most of ME's macro language constructs including constants,
  13297.  strings, arrays, arithmetic operations, compiler directives (such as
  13298.  #include), flow-control statements (for, while, if-then-else, and switch),
  13299.  function calls, global variables, local variables, parameters, and built-
  13300.  in functions. You could change this example to conform to different
  13301.  languages or different compilers; this would usually require the
  13302.  modification of just five or six lines. The ability to code macros in a C-
  13303.  like language is a strong selling point for ME. Most other editors that
  13304.  come with a macro language usually contain some sort of LISP subset for
  13305.  this, and the result is many times more complicated to write, read, and
  13306.  debug. Because of the popularity of the C language, most programmers
  13307.  will find ME's macro language extremely easy to use.
  13308.  
  13309.  ME also supports keyboard remapping: the program keeps track of key
  13310.  values so that any keystroke can be bound to another key, an ME macro, a
  13311.  user macro, or even another keyboard map. This is useful for generating
  13312.  multiple-key command sequences, either for a new group of macros or for
  13313.  emulating other editors. An interesting new feature in Version 2.0 will
  13314.  be a "typeamatic" mode for keyboard repetition.
  13315.  
  13316.  Version 2.0 of ME includes two notable changes to the macro language. One
  13317.  is the ability to use strings as the targets of a switch/case statement,
  13318.  making the macro language more powerful and efficient. Before Version
  13319.  2.0, a user could only execute a switch statement that evaluated to a
  13320.  numeric argument, and that ruled out all strings, including those
  13321.  containing the ASCII values for the digits 0 through 9. That meant that
  13322.  any selections performed with strings had to be done either via if
  13323.  statements or by encoding a numeric value in an array that also had a
  13324.  string counterpart and then using the array index to access the string.
  13325.  Now you can switch directly on a string as well as specify strings as the
  13326.  target constants for each respective case statement. This is a facility
  13327.  that most high-level languages cannot handle.
  13328.  
  13329.  The other major change to the macro language is that ME now allows calls
  13330.  to C functions directly from the macro language, providing for a very
  13331.  flexible interface. You can call all routines internal to ME, and now
  13332.  you can write parts of your larger or slower macros in C. Also, if you
  13333.  need to call some of the C run-time libraries, this lets you do so without
  13334.  having to create new macros. Instead, you just add an entry to a table and
  13335.  recompile ME. (This option is available only if you've purchased the
  13336.  source code for ME, which costs $95. Magma includes a README file
  13337.  suggesting the use of MSC 4.0 or higher for any compilations.)
  13338.  
  13339.  Other additions to the language include labels, the goto statement, and
  13340.  hexadecimal constants, all of which make the ME macro language much
  13341.  richer without going overboard. The end result is that macro generation is
  13342.  more readable, structured, and easier to perform when compared with most
  13343.  of the other macro-based editors on the market, which force you to use a
  13344.  terse and error-prone LISP-like language. LISP and its subsets are
  13345.  powerful, but not very productive or familiar to many programmers.
  13346.  
  13347.  
  13348.  Regular Expressions
  13349.  
  13350.  Many concepts from the UNIX operating system and its associated toolkit
  13351.  have made their way into mainstream computing in spite of their
  13352.  terseness and complexity. One such concept is regular expressions, which
  13353.  let you build very complicated pattern-matching commands using
  13354.  metacharacters to facilitate text searches. For instance, let's say you
  13355.  wanted to search for every occurrence of "abc" in the current window
  13356.  and change it to "ABC." That's simple enough with most editors. Now let's
  13357.  add a slight twist to the search: all the occurrences of "abc" must occur
  13358.  at the very beginning of a line. Most editors would fail at such a simple
  13359.  request.
  13360.  
  13361.  Looking through ME's list of metacharacters (see Figure 1 for a sample of
  13362.  the on-line menu for Search and Substitute Commands) you may notice the "
  13363.  " character. By using this special character in the previous request you
  13364.  could now look for the pattern " abc" instead of "abc."
  13365.  
  13366.  Another problem that comes up often is swapping two columns on a line or
  13367.  table. This can be accomplished by using " \(?*\) \(?*\)" as the search
  13368.  pattern and " \2 \1" as the replacement pattern. Such a feat often
  13369.  requires writing a general-purpose conversion utility, but comes naturally
  13370.  to ME.
  13371.  
  13372.  
  13373.  Programmer-specific Features
  13374.  
  13375.  For the programmer, ME has a few unique language features. One is its
  13376.  support for autoindenting while in input mode. Another useful feature
  13377.  is the match-brace command, which finds a closing curly brace or a closing
  13378.  parenthesis if the cursor is positioned at an opening one (and vice
  13379.  versa). Compile/next scenarios are supported with macros. And although
  13380.  Magma doesn't supply any language-specific syntax templates or syntax-
  13381.  based editing even on a simple level, you can certainly write macros to
  13382.  handle this. It is surprising that templates are not provided for the C
  13383.  language; when I mentioned this to the people at Magma, they said they
  13384.  were working on it and will have it in the next release of ME.
  13385.  
  13386.  Magma provides the CTAGS utility with every copy of ME. CTAGS was
  13387.  originally created for BSD UNIX and allows you to create a file containing
  13388.  a list of filenames and line numbers of every function you use in your
  13389.  code. Later, in an editing session, you can automatically create a new
  13390.  window containing the source file of any function in the list with the
  13391.  cursor properly positioned on it merely by responding to the go-to-
  13392.  function command sequence with the function name. Still, CTAGS is limited
  13393.  in that the file it creates does not change as your source code changes.
  13394.  It can be helpful in a large programming project though, especially if you
  13395.  are unfamiliar with the project.
  13396.  
  13397.  ME's command set has some unique features. The 8-bit-mode command is used
  13398.  for entering ASCII characters that are generally classified as
  13399.  unprintable or extended, that is, those with values less than 32 and
  13400.  greater than 127. Most editors allow you to enter only one such character
  13401.  at a time, and ME is no exception. However, ME also lets you enter a
  13402.  sequence of extended commands by switching into 8-bit-mode. This is very
  13403.  handy for generating terminal escape sequences or for emulating a strange
  13404.  file format.
  13405.  
  13406.  Another interesting point about ME is its source code option. Because it
  13407.  is written in C, the curious can use the source code to learn various data
  13408.  structure concepts by looking at a real-life application. Also, you can
  13409.  convert your most heavily used macros into true built-in commands by
  13410.  modifying some of ME's internals. Finally, some programmers may be
  13411.  interested in contacting Magma for an OEM license so they can include ME
  13412.  in their product line and/or applications as well as configuring it to
  13413.  their own needs.
  13414.  
  13415.  Magma is now working on an OS/2 version of ME, which will support all
  13416.  features of Version 2.0 as well as OS/2-specific concepts. One such
  13417.  planned feature is extending ME's autosave ability so that buffers get
  13418.  saved by another task. That way your edit sessions will not be interrupted
  13419.  abruptly.
  13420.  
  13421.  
  13422.  Printing
  13423.  
  13424.  ME does not support printing. If you need to print a copy of your source
  13425.  code or documentation, you must write a macro to handle it since Magma
  13426.  doesn't provide any. This was annoying since it meant getting intimate
  13427.  with ME's macro language, but it does demonstrate how powerful and
  13428.  adaptable ME can be. For instance, I wrote a macro that picked up the
  13429.  current filename and printed out a listing of the program in the buffer
  13430.  much like the UNIX pr command. It included line numbers, page breaks, and
  13431.  a header identifying the file and the page number. One could even expand
  13432.  on such a macro to accept a list of dot commands to perform such tasks as
  13433.  centering, justifying, underlining, and boldfacing.
  13434.  
  13435.  
  13436.  Vendor Support
  13437.  
  13438.  Magma's technical support is excellent. When I called them for support,
  13439.  I was always promptly greeted by a member of the staff, and my questions
  13440.  were answered eagerly and completely. Need I say more? Further, if you
  13441.  happen to be a member of BIX, you can communicate directly with Magma
  13442.  Systems on-line.
  13443.  
  13444.  
  13445.  Overall Impression
  13446.  
  13447.  ME is one of the top programmer-oriented editors on the market. It
  13448.  contains many concepts ideal for maximizing programmer productivity and
  13449.  stands out in four areas.
  13450.  
  13451.  The first area is its command set. High among its list of features are its
  13452.  various macros accessible either on the fly or via its macro language.
  13453.  This allows for custom editing and flexibility when dealing with
  13454.  complicated or repetitious tasks. Also, its use of buffering and
  13455.  windowing offers programmers access to multiple files, a necessity even
  13456.  when working on small projects.
  13457.  
  13458.  The next area of importance is ME's consistency. Its operation is reliable
  13459.  in every aspect. In general, the user interface is well thought out and
  13460.  its behavior is not erratic. Programmers do not need to spend time trying
  13461.  to fight with their tools so that they can do their editing. Also, when
  13462.  programmers do need to reconfigure part of ME's operation, it is a very
  13463.  smooth transformation.
  13464.  
  13465.  As for speed, ME performs very quickly. Most cursor operations occur
  13466.  instantaneously, and ME is usually waiting for you instead of the other
  13467.  way around. Similarly, non-window-oriented commands are also speedy,
  13468.  including searches for regular expressions and macro functions written
  13469.  by the user.
  13470.  
  13471.  Finally, ME contains features that are usually found in more expensive
  13472.  editors costing several hundred dollars, but its price tag is only
  13473.  $39.95. I'm told that the new release, Version 2.0, will retail for
  13474.  $89.95. It is surely the most economical editor allowing for such a
  13475.  powerful environment for programmers and is surely one of the bargains
  13476.  of 1988. Even at double the price, there's no question about its
  13477.  price/performance ratio.
  13478.  
  13479.  ME is fast, complete, reliable, and productive──exactly what a
  13480.  programmer's editor should be.
  13481.  
  13482.  
  13483.  ───────────────────────────────────────────────────────────────────────────
  13484.  CompuView's VEDIT PLUS
  13485.  ───────────────────────────────────────────────────────────────────────────
  13486.  
  13487.  Randall Swan
  13488.  
  13489.  VEDIT PLUS, from CompuView, is an excellent programming editor with
  13490.  many powerful options and a high degree of customization. The program is
  13491.  very quick to load, respond, and execute all commands.
  13492.  
  13493.  The commands are organized in a logical manner, and the on-line help and
  13494.  tutorials in the documentation considerably speed up the learning
  13495.  process. VEDIT enables you to edit multiple files simultaneously in
  13496.  separate screens or windows and to store text or macros in multiple text
  13497.  registers, which saves a great deal of time.
  13498.  
  13499.  VEDIT offers you a choice of command mode, in which you type specific
  13500.  commands, or visual mode, in which you use predefined function and Ctrl-
  13501.  key combinations to specify edit functions, thus providing much
  13502.  flexibility. The editor can be extensively tuned to an individual
  13503.  programmer's environment by using the command mode to write command
  13504.  sequences and storing them as keyboard macros with specially designated
  13505.  function keys and Ctrl-key combinations.
  13506.  
  13507.  
  13508.  Documentation
  13509.  
  13510.  VEDIT PLUS has fairly substantial documentation──378 pages in a 6-by-
  13511.  81/2-inch three-ring binder. It includes a tutorial, a user guide, a
  13512.  programming guide, a section on installation, and a reference section.
  13513.  
  13514.  The tutorial covers topics ranging from simple editing operations to more
  13515.  complicated ones. There is also a 30-Minute Tutorial in the introduction
  13516.  to get started quickly. However, specific keystrokes are conspicuously
  13517.  absent from the documentation; they are described only in the on-line
  13518.  help screens. Another problem with the documentation is that it was
  13519.  written for all installations of VEDIT PLUS, including the CRT terminal
  13520.  version and various memory-mapped machine-specific versions.
  13521.  
  13522.  
  13523.  Setup
  13524.  
  13525.  To set up VEDIT PLUS, the manual tells you to simply create a VEDIT
  13526.  subdirectory and copy the one disk into it. Several things in the
  13527.  installation instructions, however, are not fully explained.
  13528.  
  13529.  An INSTALL.EXE program allows for extensive customization of VEDIT.
  13530.  From a menu in INSTALL called Main Menu Tasks, you can modify the
  13531.  keyboard layout, add keystroke macros, and change print, edit, file-
  13532.  handling, and screen display parameters. You can also change edit switch
  13533.  settings, visual settings, and the command mode interface. It is
  13534.  possible to create numerous configurations by running INSTALL
  13535.  VPLUS.COM "outfile".COM, each identified by a different sign-on message.
  13536.  
  13537.  
  13538.  On-line Help
  13539.  
  13540.  On-line help is easily accessible by pressing Alt-F1, though this
  13541.  keystroke is explained only in the introductory tutorial. The first
  13542.  screen displays all the edit functions with corresponding keystrokes, the
  13543.  next screen displays the separate default and expert keyboard layouts,
  13544.  and the third displays status line messages and editing tasks. You can
  13545.  access on-line help by entering command mode and typing EH to see a list
  13546.  of topics or H to display five screens of information: basic and text
  13547.  register commands; extended, jump, operating system, and print commands;
  13548.  miscellaneous and numeric register commands; keyboard control, command
  13549.  modifiers, help aids, generic/indirect, numeric specifiers, and
  13550.  miscellany; and numeric, relational, and logical operators and operator
  13551.  precedence.
  13552.  
  13553.  You can skip the menu by typing H followed by a command to display a
  13554.  description of that particular command, or EH followed by a task name to
  13555.  display a description of commands needed for that particular task. You
  13556.  can select an individual command or function from any of the help screens
  13557.  and obtain a detailed description by typing its name.
  13558.  
  13559.  After selecting any individual item, however, pressing any other keystroke
  13560.  returns to the text rather than back to the help screen, which makes it
  13561.  difficult to look up more than one item at a time. There is also no way to
  13562.  exit back to the editor without either selecting a particular item or
  13563.  paging through all the help screens by hitting Return. You can edit and
  13564.  expand all three help files.
  13565.  
  13566.  One small source of confusion is that the two preconfigured keyboard
  13567.  layouts, default and expert, use different keys to define the same
  13568.  functions. The expert layout offers more predefined functions. The
  13569.  explanations in the help functions depend on which layout has been
  13570.  installed. There are no pop-up or pull-down help menus, nor is the help
  13571.  context-sensitive. You cannot access the help screens in the middle of a
  13572.  command.
  13573.  
  13574.  
  13575.  Basic Editor Functions
  13576.  
  13577.  Cursor movement is performed with the standard up, down, right-, and
  13578.  left-arrow keys. The Goto function allows for cursor movement to the
  13579.  beginning or end of the file, to a specific line number, or to previously
  13580.  set markers in the text. In general, though, it is not possible to move
  13581.  the cursor to an empty part of the screen.
  13582.  
  13583.  You have a choice of three modes for cursor movement: mode 0 causes the
  13584.  cursor to move left from the end of a long line to the end of a shorter
  13585.  line; mode 1 (default) allows the cursor to move straight up and down
  13586.  regardless of line length but moves the cursor left if new text is added
  13587.  when the cursor is beyond the end of the line; and mode 2 allows straight
  13588.  up and down movement but adds new text at the cursor position, inserting
  13589.  spaces between the end of the line and the new text.
  13590.  
  13591.  VEDIT doesn't allow you to define columns, but in other respects the block
  13592.  functions are very flexible. There are 36 text registers, labeled 0 to 9
  13593.  and A to Z, into which blocks of text can be moved or copied, either
  13594.  overwriting or appending it (using a plus sign) to any text already in
  13595.  the register. However, this operation is cumbersome since the command
  13596.  must be invoked three times, with a different sequence of keystrokes each
  13597.  time.
  13598.  
  13599.  The defined block is not highlighted, so the only way to see the
  13600.  beginning of the block is to repeat the block function (F9) followed by S
  13601.  for the Swap option. Inserting a block displaces any text on that line to
  13602.  the right. There are no functions to move or copy text without defining a
  13603.  block, but the RC command copies a specific line or range of characters
  13604.  in the edit buffer to a specified register.
  13605.  
  13606.  The search-and-replace functions offer a number of options. For example,
  13607.  both the Find (F2) and Replace (Alt-F2) functions allow you to use a
  13608.  previously defined search-and-replace string and start the search at the
  13609.  beginning of the edit buffer.
  13610.  
  13611.  The default setting for search-and-replace is not case-sensitive but can
  13612.  be set to distinguish between upper- and lowercase. Pattern-matching
  13613.  codes, such as |A for any letter, can also be used in search-and-replace
  13614.  functions.
  13615.  
  13616.  You can set wordwrap in Task 5 (Change Edit Parameters) of the INSTALL
  13617.  program, using the EP command, or selecting the Word Wrap option from
  13618.  the User function to specify a right margin, or 0 for no wordwrap. The
  13619.  default value for wordwrap is 0 in the preconfigured editor version,
  13620.  which is a little disconcerting when one is first using VEDIT.
  13621.  
  13622.  Paragraphs are formatted by using Indent or Undent to specify the left
  13623.  margin, setting wordwrap and justification, and then selecting Format
  13624.  Paragraph. The indentation (tab or offset) of the first line of the
  13625.  paragraph is preserved.
  13626.  
  13627.  Automatic conversion of upper-to-lowercase and vice versa is done by
  13628.  selecting the Uc/lc option of the Misc function to convert the character
  13629.  at the cursor. CompuView provides a V-SPELL spelling checker for any
  13630.  files created with VEDIT.
  13631.  
  13632.  
  13633.  User Interface
  13634.  
  13635.  All 10 of the function keys, most of the Alt-function-key combinations,
  13636.  and some Ctrl-key combinations are defined in the two preconfigured
  13637.  keyboard layouts, default and expert. The normal displayable characters
  13638.  and the Enter key (Ctrl-M) cannot be redefined, but all of the function
  13639.  keys, sequences of Ctrl- or Alt-letter combinations, or any escape
  13640.  sequence can be redefined by running INSTALL and selecting Modify Keyboard
  13641.  Layout.
  13642.  
  13643.  The default assignments can all be cleared or retained. Each edit function
  13644.  name, followed by the current assignment, is prompted in sequence and can
  13645.  be retained as is by pressing Enter or reassigned to a different function
  13646.  key, Ctrl-key combination, or escape sequence.
  13647.  
  13648.  The normal visual edit mode displays a status line at the top of the
  13649.  screen that shows the current line number, column number, and filename.
  13650.  The status line is replaced by the options of any edit function that is
  13651.  invoked or an error message. Switching into command mode puts "Command" in
  13652.  the status line and a "Command:" prompt at the bottom of the screen.
  13653.  
  13654.  You can set the number of lines for paging and lines displayed (three by
  13655.  default) above and below the cursor as the screen scrolls, the column for
  13656.  the horizontal scroll margin, and the continuation character (the hyphen
  13657.  by default) used for automatic wordwrap when the horizontal scroll margin
  13658.  is reached to different values by running INSTALL.
  13659.  
  13660.  Reverse video on monochrome systems and the colors on CGA or EGA systems
  13661.  can be set to new defaults by running Task 10 in INSTALL (Change Screen
  13662.  Dependent Parameters) or by using the YEA command to change both the
  13663.  foreground and background colors of the current window.
  13664.  
  13665.  VEDIT supports any screen size up to 70-lines-by-250-columns. You can set
  13666.  the size by running INSTALL and changing the number of screen lines and
  13667.  length of displayed line in Task 10 and the screen line length in Task 11
  13668.  (Additional Memory Mapped Installation Features) as well as setting
  13669.  Using High Speed EGA Color Board to Yes.
  13670.  
  13671.  
  13672.  Printing
  13673.  
  13674.  VEDIT offers such standard page layout capabilities as margins, paragraph
  13675.  formatting, and justification. CompuView also markets a more
  13676.  sophisticated page formatter program called V-PRINT (not included with
  13677.  the editor) that can create headings, tables of contents, and indexes, do
  13678.  complete paragraph formatting, and interpret page formatter commands in
  13679.  VEDIT text files.
  13680.  
  13681.  You can print text by issuing a PR command, which puts the filename and
  13682.  page number at the top of each page, or by loading the PRINT.VDM macro,
  13683.  which performs simple print formatting. The only way to print line
  13684.  numbers, headers, and footers is by modifying the PRINT.VDM macro.
  13685.  
  13686.  Line numbering can be automatically added to the file by creating a
  13687.  simple command macro to set the starting line number, then creating a loop
  13688.  to insert the line number at the beginning of the line, add a specified
  13689.  increment, and find the next line. VEDIT PLUS does not support special
  13690.  features, such as different fonts.
  13691.  
  13692.  
  13693.  Unique Language Features
  13694.  
  13695.  Language-specific editing features are found under the Misc menu.
  13696.  Positioning the cursor on the braces, brackets, angle brackets, or
  13697.  parentheses and selecting the Match Parentheses option moves the cursor
  13698.  forward or backward to the matching pair, showing any syntax errors.
  13699.  
  13700.  The automatic indenting mode can be set with the ES command to enable Edit
  13701.  Switch 3 or by choosing the User function and selecting Auto-Indent. Each
  13702.  new line will automatically indent the same amount as the previous
  13703.  line.
  13704.  
  13705.  
  13706.  Macro Capabilities
  13707.  
  13708.  VEDIT macros can be stored as command macros in a text register and
  13709.  executed with the Macro function or the RL command and the M command
  13710.  followed by the register number to load and execute the macro in the
  13711.  specified register. You can build keystroke macros by running Task 3 of
  13712.  the INSTALL program or by using the Define function (Ctrl-D) and executing
  13713.  the macro by pressing the defined function or Ctrl-key combination.
  13714.  
  13715.  Text register macros are temporary unless you save the macro or add the
  13716.  sequence of commands, including a command to copy to a specific register
  13717.  to the VEDIT.INI file.
  13718.  
  13719.  A keystroke macro can be any combination of a sequence of command mode
  13720.  commands, visual mode edit functions, or keystrokes. Text register macros,
  13721.  however, can only be sequences of one or more commands. The command mode
  13722.  used with macros is, in effect, a special macro language, since a macro
  13723.  can combine any series of commands into a single function, like a small
  13724.  program.
  13725.  
  13726.  Several macros are provided with the editor and defined in the
  13727.  preconfigured default keyboard layout, and there are even more with the
  13728.  expert layout. The documentation also has several examples of command
  13729.  macros. You can define any number of additional or replacement macros and
  13730.  assign them to any function key or Ctrl- or Alt-letter combination. With
  13731.  a little effort, any user-defined macro can be added to the definitions on
  13732.  the help screens. You can also give a command macro a meaningful name and
  13733.  save it as a file with the Register Save command, but this is not
  13734.  possible with a macro that includes functions and recorded keystrokes.
  13735.  
  13736.  
  13737.  File Handling
  13738.  
  13739.  The maximum file size for editing in VEDIT is half the available disk
  13740.  space, since both an input and an output file are required. If the input
  13741.  and output files are on different drives, the maximum size is the whole
  13742.  disk. If backward disk buffering is used, the maximum size is one third
  13743.  of the disk size, since temporary equivalents to input and output files
  13744.  must be created.
  13745.  
  13746.  Automatic forward and backward disk buffering handles large files by
  13747.  reading a section of the file, called a file page, into the edit buffer,
  13748.  then writing it to an output file when it reaches the end of the edit
  13749.  buffer and reading in another file page. The size of the file page is 8Kb
  13750.  by default. Backward disk buffering reads a file page from the output
  13751.  file back into the edit buffer, writing text from the end of the edit
  13752.  buffer out to a temporary disk file; forward buffering subsequently
  13753.  reads first from the temporary file, then from the input file.
  13754.  
  13755.  You can merge files by using the EG command to specify an entire file or a
  13756.  line range to extract from one file and insert into the file being edited.
  13757.  The EL command displays the file or specific portion with line numbers. A
  13758.  section of text can be written from one file into one of the text
  13759.  registers and placed in a second file if there is not enough memory to
  13760.  reserve a 64Kb segment for EA-EZ.
  13761.  
  13762.  To create windows you split the current window in half horizontally or
  13763.  vertically. The number of possible windows is limited by the screen size,
  13764.  since the minimum window size is one line and 15 columns, plus a border,
  13765.  and there are no overlapping windows. Seventy windows are possible on a
  13766.  standard 80-column-by-24-line screen.
  13767.  
  13768.  Windows are independently configurable as to size, number of lines or
  13769.  columns, and foreground/background colors, and each is given a one-
  13770.  character name shown in the top border line and highlighted when the
  13771.  window is active.
  13772.  
  13773.  The Window function allows you to create a window, specifying top,
  13774.  bottom, left or right position, number of lines or columns, and the name
  13775.  of the window. You can also delete a window, switch between windows, and
  13776.  zoom the current window up to full screen size, which is a very nice
  13777.  feature.
  13778.  
  13779.  One helpful feature is the option of using the command mode as an on-line
  13780.  calculator for integer calculations or finding the numeric value of any
  13781.  ASCII character. The only drawback is that it cannot handle decimal or
  13782.  fractional values.
  13783.  
  13784.  VEDIT is seriously limited in that it does not provide for invoking a
  13785.  compiler from within the editor or returning to the text with the cursor
  13786.  positioned on the particular text line when a compiler error occurs.
  13787.  
  13788.  
  13789.  Conclusion
  13790.  
  13791.  VEDIT is a very good editor but I do have some complaints about it, mostly
  13792.  concerning the lack of a direct link to a compiler to run from the editor
  13793.  or return directly to error lines and certain organizational features that
  13794.  should be improved.
  13795.  
  13796.  There should only be one preconfigured keyboard layout; providing two
  13797.  different layouts is unnecessary and confusing. The functions really
  13798.  ought to be listed alphabetically for easy reference in both the help
  13799.  screens and the documentation, and specific function and Ctrl-key
  13800.  combinations should be described in the documentation as default values
  13801.  that can be changed. The command mode, though very powerful, is
  13802.  unnecessarily cryptic because the command names are often letter
  13803.  combinations that are arbitrary rather than mnemonic. The functions that
  13804.  provide one-line option menus when selected should be identified as such.
  13805.  Several of these functions, such as Misc or User, have names that give no
  13806.  indication of the options that lie beneath them.
  13807.  
  13808.  Still, VEDIT's many options- speed, ability to switch between command and
  13809.  visual modes, and the ease with which you can customize it to meet your
  13810.  own needs-make it a powerful, flexible, and efficient editor. It is
  13811.  highly recommended.
  13812.  
  13813.  
  13814.  Figure 1:  This sample macro script will compile Microsoft C Version 4.0
  13815.             programs and report any errors found.
  13816.  
  13817.  /*MSCCOMP - this file contains macros for automating the compile and
  13818.  * error-search procedures under Microsoft 4.0. To compile the
  13819.  * current buffer, press Alt-C. To search for errors, press Ctrl-N.
  13820.  *
  13821.  * Written by Marc Adler of MAGMA SYSTEMS. January 1987
  13822.  */
  13823.  
  13824.  #define ALT_C   174
  13825.  #define CTRL_N  14
  13826.  
  13827.  #define NO      0
  13828.  #define YES     1
  13829.  
  13830.  int reached_eof;
  13831.  
  13832.  init()
  13833.  {
  13834.    assign_key("compile",    ALT_C);
  13835.    assign_key("next_error", CTRL_N);
  13836.  }
  13837.  
  13838.  compile()
  13839.  {
  13840.    string currfile, extension;
  13841.    string errmsg;
  13842.    int    i;
  13843.  
  13844.    errmsg = "Error - the file is not a C file. <ENTER to continue>";
  13845.  
  13846.    currfile = filename();
  13847.  
  13848.    /* Make sure that the current buffer is a C file */
  13849.    if ((i = index(currfile, ".")) > 0)         /* get the extension */
  13850.    {
  13851.      extension = substr(currfile, i+1, 3);
  13852.      if (extension != "c" && extension != "C") /* is it a C file?   */
  13853.      {
  13854.        get_tty_str(errmsg);                    /* NO!!! Error!!!    */
  13855.        return;
  13856.      }
  13857.    }
  13858.    else            /* the buffer has no extension, so it's an error */
  13859.    {
  13860.      get_tty_str(errmsg);
  13861.      return;
  13862.    }
  13863.  
  13864.    writefile(currfile);    /* save the current version & compile it */
  13865.    set_buffer_modified(currbuf(), 0);
  13866.    os_command(sprintf("msc /AL /Zi /J %s; > errs", currfile));
  13867.    next_error();           /* see if there are errors               */
  13868.  }
  13869.  
  13870.  
  13871.  next_error()
  13872.  {
  13873.    int id, old_id;
  13874.    int n;
  13875.    string cl, foo;
  13876.  
  13877.    old_id = currbuf();          /* save the id of the source buffer */
  13878.  
  13879.    if ((id = find_buffer("errs")) == 0)
  13880.    {
  13881.      id = create_buffer("errs");
  13882.      reached_eof = NO;
  13883.    }
  13884.    id = setcurrbuf(id);
  13885.  
  13886.    /* Microsoft C 4.0 puts out error messages in this format:      */
  13887.    /*      filename(line #) : messagetype error# message           */
  13888.    if (reached_eof == NO && fsearch("([0-9][0-9]*) : ?*error") > 0)
  13889.    {
  13890.      n = atoi(substr(cl = currline(), currcol()+1, 5));
  13891.                                  /* get line # */
  13892.      gobol();                    /* move to the next error */
  13893.      if (!down())
  13894.        reached_eof = YES;
  13895.      show_buffer(old_id);        /* go to the source buffer and */
  13896.      goline(n);                  /* move to the offending line */
  13897.      message(cl);                /* display the error message */
  13898.      get_tty_char();             /* and let the user acknowledge */
  13899.    }
  13900.  
  13901.    else          /* Reached the end of the error file */
  13902.    {
  13903.      foo = get_tty_str("NO MORE ERRORS");
  13904.      delete_buffer(id);          /* get rid of the error buffer */
  13905.      show_buffer(old_id);        /* and set the current buffer */
  13906.    }
  13907.  }
  13908.  
  13909.  
  13910.  ───────────────────────────────────────────────────────────────────────────
  13911.  Inside ME
  13912.  ───────────────────────────────────────────────────────────────────────────
  13913.  
  13914.  Marc Adler
  13915.  
  13916.  ME was born out of frustration with current editing environments.
  13917.  Programmers always have a wish list of features that they would like in
  13918.  their editing environment, but which their editor does not support.
  13919.  Magma's goal was to develop an editor that offered both flexibility and
  13920.  extensibility. Users would then be able to create an environment tailor-
  13921.  made to meet their specific programming needs.
  13922.  
  13923.  The ability to reconfigure was at the top of our list when working on the
  13924.  design for a new editor. This meant the editor needed a macro language
  13925.  that would allow one to write new commands. Unfortunately, most of the
  13926.  editors that provide macro capability have LISP or TECO-like macro
  13927.  languages which are extremely cumbersome to use. The growing
  13928.  popularity of the C language led us to believe that our greatest
  13929.  audience would be C programmers, and thus we focused our attention on
  13930.  their needs. This eliminated the need to put thousands of parentheses in
  13931.  ME's macro code, as would be required by the LISP syntax. However, a macro
  13932.  language should be at a "higher level" than a traditional third
  13933.  generation programming language such as C. The programmer should not
  13934.  have to worry about pointers, addresses, and memory allocation when
  13935.  creating a macro. Therefore, we decided that our macro language would
  13936.  differ from C with respect to the ease of programmability.
  13937.  
  13938.  We also wanted the macros to run as quickly as possible. Therefore a macro
  13939.  would need to be compiled into a low-level intermediate code before being
  13940.  executed by the editor's interpreter. The intermediate code would contain
  13941.  opcodes for assignment, control flow, and procedure invocation, and would
  13942.  be low-level enough that an intermediate code file could be turned into an
  13943.  assembly language routine and linked in with the editor.
  13944.  
  13945.  Dynamic typing of variables is one feature that saves the macro
  13946.  programmer a bit of time. Variable declarations are usually at the
  13947.  programmer's discretion, but in our design the macro interpreter itself
  13948.  figures out the correct typing at run-time.
  13949.  
  13950.  String manipulation should be performed as easily as possible. ME's
  13951.  macro language relieves C programmers from constant worry about memory
  13952.  allocation when dealing with strings by performing all memory allocation
  13953.  internally.
  13954.  
  13955.  
  13956.  Linking in C Functions
  13957.  
  13958.  To expand their environments, users should have access to windowing,
  13959.  database, and possibly communications libraries. The ability to
  13960.  integrate these functions at the macro language level is very desirable.
  13961.  Unfortunately, if all of this functionality were put directly into ME's
  13962.  kernel, the resulting size would make the editor very unwieldy.
  13963.  
  13964.  The ability to call external C functions from within the macro language
  13965.  was added in version 2.0 of ME in order to make the editor truly
  13966.  reconfigurable. A table was provided to let ME users add C functions to
  13967.  the editor's database of callable routines. To add an external function to
  13968.  the macro language, two items are inserted into this table-the name of the
  13969.  function and a reference to the function. For instance, if a programmer
  13970.  wants to add a database routine called db_create(), then an entry such as
  13971.  the following is added to the table:
  13972.  
  13973.    { "create_database", db_create }
  13974.  
  13975.  After adding this entry, the table needs to be recompiled and linked into
  13976.  the editor.
  13977.  
  13978.  Whenever a programmer wants to access this function in the macro language,
  13979.  the line
  13980.  
  13981.    create_database("my_db");
  13982.  
  13983.  is coded into the macro program. The macro interpreter, upon seeing this
  13984.  function, looks at the external function table and calls the function.
  13985.  
  13986.  
  13987.  OS2 and Porting
  13988.  
  13989.  ME is written almost completely in the C ANSI standard. This allows the
  13990.  source to be compiled by any compiler supporting the ANSI standard.
  13991.  
  13992.  ME will soon support OS/2 protected mode. OS/2's Family API was designed
  13993.  so that most DOS applications could easily be ported to protected mode.
  13994.  Since ME uses the standard C memory allocation calls, one immediate
  13995.  benefit will be the increased file size that the user will be able to
  13996.  edit. At the macro language level, support will be provided for the user
  13997.  to access dynamic-link libraries.
  13998.  
  13999.  
  14000.  Future Directions
  14001.  
  14002.  Since ME is available with full source code, Magma Software Systems is
  14003.  anxious to have its users determine our future direction. Various
  14004.  applications have been written in ME's macro language, including a DBASE
  14005.  programming system, and a Computer-Aided Software Engineering (CASE)
  14006.  system. The ability to access external C functions will allow our users
  14007.  to be more creative in their applications. We will work with our users to
  14008.  insure that ME will be able to fully support their needs.
  14009.  
  14010.  
  14011.  ───────────────────────────────────────────────────────────────────────────
  14012.  DEVELOPERS
  14013.  ───────────────────────────────────────────────────────────────────────────
  14014.  
  14015.  BRIEF
  14016.  Solution Systems
  14017.  541 Main St., Suite 410B
  14018.  South Weymouth, MA 02190
  14019.  (617) 337-6963 or (800) 821-2492
  14020.  
  14021.  Epsilon Editor
  14022.  Lugaru Software
  14023.  5740 Darlington Rd.
  14024.  Pittsburgh, PA 15217
  14025.  (412) 421-5911
  14026.  
  14027.  KEDIT
  14028.  Mansfield Software Group, Inc.
  14029.  P.O. Box 432
  14030.  Storrs, CT 06268
  14031.  (203) 429-8402
  14032.  
  14033.  ME
  14034.  Magma Software Systems
  14035.  138-23 Hoover Ave.
  14036.  Jamaica, NY 11435
  14037.  (201) 792-3954
  14038.  
  14039.  MICRO/SPF
  14040.  Phaser Systems, Inc.
  14041.  115 Sansome St.
  14042.  San Francisco, CA 94104
  14043.  (415) 434-3990
  14044.  
  14045.  The Norton Editor
  14046.  Peter Norton Computing, Inc.
  14047.  2210 Wilshire Blvd., #186
  14048.  Santa Monica, CA 90403
  14049.  (213) 453-2361
  14050.  
  14051.  PC/VI
  14052.  Custom Software Systems (CSS)
  14053.  P.O. Box 678
  14054.  Natick, MA 01760
  14055.  (617) 653-2555
  14056.  
  14057.  PMATE
  14058.  Phoenix Technologies, Ltd.
  14059.  320 Norwood Park South
  14060.  Norwood, MA 02062
  14061.  (617) 769-8310
  14062.  
  14063.  Uni Press Emacs
  14064.  Uni Press Software
  14065.  2025 Lincoln Highway
  14066.  Edison, NJ 08817
  14067.  (800) 222-0550
  14068.  
  14069.  VEDIT PLUS
  14070.  CompuView Products, Inc.
  14071.  1955 Pauline Blvd.
  14072.  Ann Arbor, MI 48103
  14073.  (313) 996-1299
  14074.  
  14075.  WordPerfect Program Editor
  14076.  (part of the WordPerfect Library)
  14077.  WordPerfect Corporation
  14078.  288 West Center St.
  14079.  Orem, Utah 84057
  14080.  (801) 226-7877
  14081.  
  14082.  XTC
  14083.  Wendin
  14084.  Box 3888
  14085.  Spokane, WA 99220-3888
  14086.  (509) 624-8088
  14087.  
  14088.  
  14089.  ════════════════════════════════════════════════════════════════════════════
  14090.  
  14091.  
  14092.  Vol. 3 No. 3 Table of Contents
  14093.  
  14094.  SQLWindows Brings a Graphical User Interface to SQL Database Applications
  14095.  
  14096.  Gupta Technologies, Inc., will soon release SQLWindows, an innovative
  14097.  developement system that makes it easier to create database applications
  14098.  by combining the graphic user interface of Windows with the power of
  14099.  programming in SQL. We talked with the designers of this new product.
  14100.  
  14101.  
  14102.  The Graphics Programming Interface: A Guide to OS/2 Presentation Spaces
  14103.  
  14104.  The Graphics Programming Interface (GPI) of the OS/2 Presentation Manager
  14105.  (PM) introduces many new concepts for programmers. Charles Petzold examines
  14106.  the three types of presentation space, data structures internal to the PM
  14107.  that act as a passport to using its graphics functions.
  14108.  
  14109.  
  14110.  Using OS/2 Semaphores to Coordinate Concurrent Threads of Execution
  14111.  
  14112.  OS/2 semaphores are an interprocess communication facility for coordinating
  14113.  concurrent threads of execution. They can serialize access to code and
  14114.  allow a thread to signal that an event has occured. This article looks at
  14115.  the four types of semaphores; exclusive system, nonexclusive system, RAM,
  14116.  and FSRam.
  14117.  
  14118.  
  14119.  Design Concepts and Considerations in Building an OS/2 Dynamic-Link Library
  14120.  
  14121.  Dynamic-link libraries are among the most valuable facilities of OS/2. They
  14122.  permit multiple applications to share only one copy of a function. This
  14123.  article provides programmers with a road map that demonstrates that writing
  14124.  a dynamic-link library is not as complicated as it may seem at first glance.
  14125.  
  14126.  
  14127.  New Compiler Technology Boosts Microsoft(R) QuickBASIC 4.0 Productivity
  14128.  
  14129.  Using proprietary threaded pseudocode technology, the latest version of
  14130.  Microsoft QuickBASIC offers the best features of both a compiler and an
  14131.  interpreter, eliminating the need to choose between the two. It provides
  14132.  an instant, integrated programming environment.
  14133.  
  14134.  
  14135.  Debug Microsoft(R) Windows Programs More Effectively with a Simple Utility
  14136.  
  14137.  Unfortunately, many traditional debugging techniques don't work in the
  14138.  Windows environment. The DEBUG utility provides print statementlike
  14139.  debugging within Windows. The utility will also record and display
  14140.  internal program events and lets you manage the process with a central
  14141.  control panel.
  14142.  
  14143.  
  14144.  An Examination of the Operating Principles of the Microsoft Object Linker
  14145.  
  14146.  The Microsoft Object Linker (LINK) is a powerful development tool that
  14147.  supports segment ordering, run-time memory management, and dynamic overlays.
  14148.  We discuss the details of how LINK works to resolve memory references for
  14149.  more efficient and simpler programs.
  14150.  
  14151.  
  14152.  EDITOR'S NOTE
  14153.  
  14154.  This issue's lead article takes and advance look at Gupta's SQLWindows
  14155.  product, a fine example of systems engineering using Microsoft Windows(R)
  14156.  to provide a graphical user interface for the building of end-user-programs.
  14157.  It will be one of the first commercial systems to exploit the client/server
  14158.  application architecture that is fundamental to distributed processing.
  14159.  
  14160.  In a short period of time, the SQL language has become an important computer
  14161.  standard and implementations now exist on a wide variety of mainframe and
  14162.  minicomputer systems. SQL is also available on personal computer systems
  14163.  in a variety of incarnations. The Database Manager included in the IBM(R)
  14164.  Extended Edition of OS/2 may provide the most important implementation of
  14165.  SQL, since it is part of Systems Application Architecture (SAA). Products
  14166.  competing with the IBM offering, including the Ashton-Tate/Microsoft SQL
  14167.  Server, the Oracle Database Add-In for 1-2-3(R), and the Gupta SQL products
  14168.  discussed here, use the same SQL statements and process them in the same
  14169.  way. This allows software developers to build compatible applications that
  14170.  can access data on a variety of servers.
  14171.  
  14172.  We will present a variety of articles on SQL in coming issues that will
  14173.  concentrate on how SQL can serve as a bridge between the application you
  14174.  develop and the variety of SQL databases on local and remote systems.
  14175.  
  14176.  This issue also takes a look at designing OS/2 dynamic-link libraries,
  14177.  shareable libraries that run efficiently in a multitasking environment.
  14178.  A guide to three OS/2 presentation spaces provides some helpful tips on
  14179.  choosing the right one for your application. Four types of OS/2 semaphores
  14180.  used for coordinating concurrent threads of execution are explored. We
  14181.  examine the new compiler technology in Microsoft QuichBASIC Version 4.0
  14182.  and debug windows applications with a utility that lets you manage program
  14183.  debugging more effectively.
  14184.  
  14185.  Look for the DEBUG utility code and all our source code listings on DIAL,
  14186.  CompuServe(R), and two public access bullitin boards. On the East Coast,
  14187.  users can call (212) 889-6438 to join the RamNet bulletin board. On the
  14188.  West Coast, call (415) 284-9151 for the ComOne bulletin board. In either
  14189.  case, look for the MSJ directory.──Ed.
  14190.  
  14191.  
  14192.  Masthead
  14193.  
  14194.  JONATHAN D. LAZARUS
  14195.  Editor and Publisher
  14196.  
  14197.  EDITORIAL
  14198.  
  14199.  TONY RIZZO
  14200.  Technical Editor
  14201.  
  14202.  KAREN STRAUSS
  14203.  Assistant Editor
  14204.  
  14205.  JOANNE STEINHART
  14206.  Production Editor
  14207.  
  14208.  GERALD CARNEY
  14209.  Staff Editor
  14210.  
  14211.  KIM HOROWITZ
  14212.  Editorial Assistant
  14213.  
  14214.  ART
  14215.  
  14216.  MICHAEL LONGACRE
  14217.  Art Director
  14218.  
  14219.  VALERIE MYERS
  14220.  Associate Art Director
  14221.  
  14222.  CIRCULATION
  14223.  
  14224.  WILLIAM B. GRANBERG
  14225.  Circulation Manager
  14226.  
  14227.  L. PERRIN TOMICH
  14228.  Assistant to the Publisher
  14229.  
  14230.  DONNA PUIZINA
  14231.  Administrative Assistant
  14232.  
  14233.  Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
  14234.  in part or in whole without permission is prohibited.
  14235.  
  14236.  Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
  14237.  NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
  14238.  Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
  14239.  President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
  14240.  William Neukom, Secretary.
  14241.  
  14242.  Microsoft Corporation assumes no liability for any damages resulting from
  14243.  the use of the information contained herein.
  14244.  
  14245.  Microsoft, MS-DOS, MS XENIX, and CodeView are registered trademarks of
  14246.  Microsoft Corporation. QuickC is a Trademark of Microsoft Corporation.
  14247.  CompuServe is a registered trademark of CompuServe Incorporated. dBase
  14248.  is a registered trademark of Ashton-Tate Corporation. Hayes is a registered
  14249.  trademark of Hayes Microcomputer Products, Inc. IBM and PC/AT are registered
  14250.  trademarks of International Business Machines Corporation. Macintosh is a
  14251.  registered trademark of Apple Computer, Inc. 1-2-3 is a registered trademark
  14252.  of Lotus Development Corporation. PostScript is a registered trademard of
  14253.  Adobe Systems, Inc. UNIX is a registered trademark of American Telephone
  14254.  and Telegraph Company. WordStar is a registered trademark of MicroPro
  14255.  International Corporation.
  14256.  
  14257.  ████████████████████████████████████████████████████████████████████████████
  14258.  
  14259.  SQLWindows Brings a Graphical User Interface to SQL Database Applications
  14260.  
  14261.  ───────────────────────────────────────────────────────────────────────────
  14262.  Also see the following related articles:
  14263.    SQL Server
  14264.    Inside Microsoft Windows
  14265.  ───────────────────────────────────────────────────────────────────────────
  14266.  
  14267.  Craig Stinson
  14268.  
  14269.  Gupta Technologies, Inc., the Menlo Park, Calif., vendor of distributed
  14270.  database software, is preparing to release a one-of-a-kind Microsoft(R)
  14271.  Windows application called SQLWindows. The product is an application
  14272.  development system specialized for database engines based on structured
  14273.  query language (SQL). It is designed to let programmers create data entry
  14274.  and manipulation tools that combine the user interface sophistication
  14275.  of Windows on the front end with the power and universality of SQL on the
  14276.  back end.
  14277.  
  14278.  Scheduled for shipment in July, SQLWindows is the third link in a triad of
  14279.  data management products that Gupta Technologies calls The SQL System.
  14280.  The other two components are SQLBase, a database server tailored for the
  14281.  80286/80386 environment, and SQLNet, which is a transparent gateway to
  14282.  IBM's DB2 (future versions will offer connectivity to other mainframe DBMS
  14283.  engines, as well).
  14284.  
  14285.  Gupta Technologies was founded in 1984 by Umang P. Gupta and D. Bruce
  14286.  Scott, two former key players at Oracle Corp. Gupta was the vice president
  14287.  and general manager of the microcomputer products division at Oracle, and
  14288.  Scott is coauthor of the Oracle database management system.
  14289.  
  14290.  The company's first product, SQLBase, made news when it was introduced in
  14291.  1986 because it was the first true database server, as opposed to a
  14292.  multiuser DBMS running on a file server, designed specifically to run on
  14293.  Microsoft-Networks-based microcomputers. An expanded version of this
  14294.  product recently received attention when Lotus Development Corp.
  14295.  announced that it would be the engine for the forthcoming Lotus/DBMS
  14296.  product.
  14297.  
  14298.  Lotus will, of course, supply its own application development facilities
  14299.  for Lotus/DBMS. However, because the Lotus engine will be essentially the
  14300.  same product as SQLBase and will incorporate the same API, applications
  14301.  written in any language to run under SQLBase will run unchanged under
  14302.  Lotus/DBMS. This includes applications that are written in Gupta's
  14303.  SQLWindows.
  14304.  
  14305.  "SQLWindows works with SQLBase today and with the future Lotus/DBMS
  14306.  Server," says Umang Gupta. "And in the future we expect to provide similar
  14307.  support for other database servers. A design objective from the very
  14308.  beginning has been to make the product work with other SQL engines."
  14309.  
  14310.  Gupta stresses that the forthcoming SQL Server from Microsoft and Ashton-
  14311.  Tate is among the products that are targeted for support.
  14312.  
  14313.  "SQLWindows is unique," continues Gupta. "There's no other player we know
  14314.  of that has a product in the market, or close to market, that is
  14315.  Presentation Manager- or Windows-based, engine-independent, and tied both
  14316.  to the Windows environment and SQL."
  14317.  
  14318.  A Presentation Manager version of SQLWindows is also in the works; Gupta
  14319.  believes it will be ready by the fourth quarter of 1988. After that,
  14320.  the company plans to turn its attention to the Macintosh(R).
  14321.  
  14322.  "Our long-term plans," says Gupta, "assume that the platforms will be
  14323.  PCs, PS/2(R)s running Presentation Manager or Windows, Macintoshes, or
  14324.  UNIX(R) workstations running some standardized UNIX windowing system."
  14325.  
  14326.  Covering all the front-end platforms, providing compatibility with the
  14327.  growing number of SQL engines in the marketplace, and at the same time
  14328.  expanding SQLNet to provide connectivity with other mainframe DBMS
  14329.  engines besides DB2, is, to say the least, an ambitious program. Gupta
  14330.  Technologies currently has about 25 employees, including 12 full-time
  14331.  programmers. "We plan to increase our development and marketing staff
  14332.  significantly in the very near future," says Gupta.
  14333.  
  14334.  
  14335.  Interacting Windows
  14336.  
  14337.  Gupta and Michael Geary, chief designer of SQLWindows, gave MSJ a look at
  14338.  a beta version of the company's newest product. SQLWindows consists of
  14339.  three main components: a visual forms-layout editor (similar to the
  14340.  dialog editor that comes with the Microsoft Windows Toolkit), an outline
  14341.  editor, and a programming language called SQLWindows Applications
  14342.  Language (SAL). The outline editor and layout editor function as
  14343.  interacting windows: changes made in either window immediately update
  14344.  the other window.
  14345.  
  14346.  Figure 1 shows a sample SQLWindows application in a very early stage of
  14347.  development. The programmer has created a sample "About" box. The box is
  14348.  displayed in the layout editor window (the lower window in Figure 1),
  14349.  while a portion of the corresponding source code is shown as selected in
  14350.  the outline window.
  14351.  
  14352.  The solid diamonds in the outline mark headings that have subordinate
  14353.  entries; the open diamonds indicate entries that can't be expanded
  14354.  further. In Figure 1, the "Contents" entry, which is subordinate to
  14355.  "Dialog Box: AboutBox", has been expanded to reveal two lines of text and
  14356.  a single push button.
  14357.  
  14358.  Figure 2 zooms in on a different portion of the source code for the same
  14359.  "About" box. Here the focus is on the push button, and you can see the
  14360.  button's title, its location and size attributes, its keyboard
  14361.  "accelerator" (the keystroke that will be considered equivalent to a
  14362.  mouse click on the button), and the "message actions" associated with
  14363.  this push button. In the context of this application there's only one push
  14364.  button event of interest (the user clicks on it or performs the equivalent
  14365.  action on the keyboard), therefore this object only needs a single
  14366.  message action. The message action entry says, in effect, "When the user
  14367.  clicks, terminate this dialog and return to the parent window." SAM_Click
  14368.  means the button has been clicked, and SalEndDialog and SalParentWindow
  14369.  are functions supplied in the SQLWindows Application Language.
  14370.  
  14371.  
  14372.  Progressive Disclosure
  14373.  
  14374.  Gupta emphasizes that SQLWindows is designed to serve the needs of
  14375.  programmers, and the outline approach to source code display is very much
  14376.  in keeping with that objective.
  14377.  
  14378.  "A lot of 4GLs take a sort of dialog box approach to developing code,"
  14379.  he says. "You add a field somewhere, and a dialog box pops up and asks you
  14380.  all sorts of questions." The drawback to that style of working, asserts
  14381.  Gupta, is that you can't see your code as it develops because it's all
  14382.  encapsulated in the dialog boxes. "That might suit an end user because
  14383.  an end user doesn't necessarily want to see a lot of code. But a
  14384.  programmer──at least sometimes──needs to be able to see all of it. You have
  14385.  to give him a means of doing that, a sort of progressive disclosure. An
  14386.  outline is the appropriate means."
  14387.  
  14388.  Designer Geary says the idea of incorporating an outline editor into
  14389.  SQLWindows occurred to him as he was studying documentation for some
  14390.  other application development systems that were not based on an outline.
  14391.  "I noticed in a couple of cases that they drew an outline in their
  14392.  documentation as a way of explaining what they were doing. And I thought,
  14393.  if they draw an outline to explain what they're doing, let's use an
  14394.  outline to do what we're doing."
  14395.  
  14396.  The programmer using SQLWindows can move freely between the outline editor
  14397.  and the layout window, working in whichever window is more convenient at
  14398.  any given moment. The windows are simply two representations of a common
  14399.  underlying data structure; changes made in either window alter the
  14400.  underlying structure and are immediately reflected in the other window.
  14401.  
  14402.  Figures 3 and 4, illustrate these two ways of working. In Figure 3 the
  14403.  programmer is inserting a new data field into the layout window, using one
  14404.  of the tools in the SQLWindows Draw menu. In Figure 4, the programmer has
  14405.  made a change directly to the outline by pressing the Insert key and
  14406.  typing.
  14407.  
  14408.  Note that the programmer's action in Figure 4 is inappropriate. A form
  14409.  window cannot be added to a menu, so SQLWindows objects. The
  14410.  programmer has three choices: to return to the outline and edit the
  14411.  entry (Esc), to throw out the bogus entry (No), and to accept the entry
  14412.  (Yes). If Yes is chosen, SQLWindows leaves the entry in place but converts
  14413.  it into a comment, thus allowing the programmer to attend to the problem
  14414.  later.
  14415.  
  14416.  
  14417.  Access to SQL
  14418.  
  14419.  All access to SQL takes place via 15 functions supplied in SQLWindows.
  14420.  The list includes SQLImmediate, which immediately executes an SQL
  14421.  statement; SQLPrepare, which readies an SQL statement for later
  14422.  execution; SQLConnect, which connects the application to a specified
  14423.  database; SQLDisconnect; SQLFetch, which retrieves a given row of data;
  14424.  SQLFetchNext; and SQLFetchPrevious. Figure 5 demonstrates the use of the
  14425.  SQLImmediate function.
  14426.  
  14427.  The application in Figure 5 is a database used by Geary to track bugs in
  14428.  the prerelease versions of SQLWindows. Figure 6 shows a completed form
  14429.  used as part of this database. The selected section of code is the action
  14430.  taken by the New command from the application's Bug menu. A validation
  14431.  test is performed first (ValidateInsert). If that fails, an SAL message
  14432.  box function is called. Otherwise, two calls to SQLImmediate are made──the
  14433.  first to determine the next sequential bug number (select max(bugno) +
  14434.  1), and the second to make the appropriate insertion.
  14435.  
  14436.  Gupta stresses that the SQLWindows programmer has explicit control of all
  14437.  SQL statements generated by the system. "In some other 4GLs," he
  14438.  maintains, "SQL statements are implicitly generated, and the programmer or
  14439.  user is not actually executing SQL directly. To the extent that that's the
  14440.  case, the programmer has less control, or no control. In SQLWindows, you
  14441.  can execute SQL statements to your heart's content, any time you want, in
  14442.  any order you want."
  14443.  
  14444.  
  14445.  Internal and External Functions
  14446.  
  14447.  The call to ValidateInsert in Figure 5 is an example of what SQLWindows
  14448.  terms "internal functions," functions that are not part of the system's
  14449.  built-in repertoire but can be developed by the programmer using SAL
  14450.  functions and SQL functions to suit his or her own needs. The system also
  14451.  provides for external functions, which can be either dynamic-link
  14452.  libraries or static libraries written in C.
  14453.  
  14454.  Figure 7 shows the declaration of functions stored in two external
  14455.  libraries, SQLWIN.EXE and USER.EXE, which is part of the Microsoft Windows
  14456.  Toolkit. Figure 8 illustrates the code for an internal function, DoQuery,
  14457.  that calls SQL functions to prepare, execute, and fetch one row of data.
  14458.  
  14459.  
  14460.  Why Windows?
  14461.  
  14462.  Why did Gupta Technologies choose to create an SQL front end in Microsoft
  14463.  Windows? Ask the designer and the CEO, and you get two variations of the
  14464.  same answer.
  14465.  
  14466.  Says Geary, "Windows has the user interface that I like. It's really
  14467.  oriented toward building applications that are both easy to learn and easy
  14468.  to use once you've learned them. It also gives us the tools that we can
  14469.  pass on to our SQLWindows developers to let them build applications that
  14470.  have a good, crisp feel to them."
  14471.  
  14472.  Says Gupta, "A major premise of our business is that users want unified
  14473.  access to personal, departmental, and corporate database systems and
  14474.  that SQL is the lingua franca that makes this access possible. A second
  14475.  major premise is that access to database servers will take place from
  14476.  advanced applications written to take advantage of the next generation
  14477.  of PCs. And that means from OS/2 Presentation Manager and Microsoft
  14478.  Windows-and not from dumb terminals."
  14479.  
  14480.  Clearly, the technology of "industrial-strength database management," as
  14481.  Gupta calls it, is changing dramatically these days. SQL is emerging as a
  14482.  standard medium for database access. And the arrival of 80386-based PCs
  14483.  and high-speed local area networks has enabled database servers running
  14484.  on PCs to meet departmental needs as cost effectively as minicomputer-
  14485.  based systems.
  14486.  
  14487.  Gupta Technologies has demonstrated a strong sense of direction with
  14488.  these developments. It made an early commitment to SQL and developed
  14489.  the first PC-based database server product. In Gupta's words, the company
  14490.  "made a few good calls."
  14491.  
  14492.  On the front end, advances in interface technology have signaled the slow
  14493.  beginning of the end for traditional, minicomputer-style, text-based
  14494.  4GLs. Within the next year or two, we can expect to see a myriad of new
  14495.  approaches to application development from software houses whose roots are
  14496.  planted firmly in the microcomputer world. The OS/2 systems and
  14497.  Presentation Manager will play a dominant role in this development. By
  14498.  utilizing the Microsoft Windows technology of today, Gupta and
  14499.  SQLWindows are in the position to be trendsetters in the Presentation
  14500.  Manager and OS/2 environment of the not-so-far-off future.
  14501.  
  14502.  
  14503.  ───────────────────────────────────────────────────────────────────────────
  14504.  SQL Server
  14505.  ───────────────────────────────────────────────────────────────────────────
  14506.  
  14507.  SQLWindows is the front-end interface to Gupta's SQLBase. When the Ashton-
  14508.  Tate/Microsoft SQLServer, a powerful relational database server for PC
  14509.  local area work groups, becomes available, Gupta plans to modify
  14510.  SQLWindows to serve as a front-end application for it. SQL Server is based
  14511.  on technology licensed from Sybase, and runs on top of OS/2-based networks
  14512.  and communicates with workstations running under OS/2 or DOS 3.2 or
  14513.  higher.
  14514.  
  14515.  SQL Server is based on a superset of the ANSI standard Structured Query
  14516.  Language (SQL) that applications use to communicate with relational
  14517.  databases. SQL is transparent to the user and complements languages such
  14518.  as dBASE(R), through which users gain access to data.
  14519.  
  14520.  SQL Server uses a client/server approach that cleanly splits the functions
  14521.  of a database management system into a back-end (server) component where
  14522.  data is managed and a front-end (client) component that allows data to be
  14523.  accessed by users. SQL is used as the access language between the client
  14524.  and server.
  14525.  
  14526.  The client workstation communicates with SQL Server over PC-based
  14527.  networks using TRANSACT-SQL, a superset of ANSI standard SQL with a set of
  14528.  extensions that include flow control, temporary variables, transaction
  14529.  management, precompiled SQL queries (stored procedures), and special
  14530.  stored procedures called triggers.
  14531.  
  14532.  SQL Server creates the concept of an intelligent database server,
  14533.  centralizing database intelligence by using stored procedures and
  14534.  triggers. Stored procedures minimize message and network traffic from the
  14535.  workstation to the server and can result in performance that is many
  14536.  times greater than standard SQL queries. Triggers are stored procedures
  14537.  which are automatically executed or "fired" when database values are
  14538.  inserted, updated, or deleted. Using stored procedures and triggers, SQL
  14539.  Server provides a consistent method of maintaining database integrity
  14540.  across applications.
  14541.  
  14542.  SQL Server has an open platform architecture thus allowing multiple
  14543.  workstation applications to access back-end database services. This
  14544.  gives users the ability to utilize a variety of PC applications and
  14545.  languages against the same data at the same time. A client application
  14546.  programming interface (API), DB-Library, may be used by the application
  14547.  developer to integrate a program with SQL Server.
  14548.  
  14549.  SQL Server runs as a single multithreaded process under OS/2 with the
  14550.  capability of managing many users and multiple databases
  14551.  simultaneously. This results in high transaction throughput and
  14552.  efficient use of computer resources. A 386-based SQL Server can be
  14553.  expected to support more than 35 users accessing a 300Mb database with
  14554.  response time under a second.
  14555.  
  14556.  With its transaction-oriented DBMS kernel, SQL Server makes the database
  14557.  constantly available for such administrative tasks as backup, recovery,
  14558.  database updates, design changes, and integrity rule changes even while
  14559.  users continue to access the database. SQL Server also maximizes
  14560.  transaction throughput so that performance, as seen by the individual
  14561.  user, stays virtually constant as users are added to the network. The
  14562.  system can be administered from anywhere on the network, so that there is
  14563.  no need for a separate administrator's console.
  14564.  
  14565.  ───────────────────────────────────────────────────────────────────────────
  14566.  Inside Microsoft Windows
  14567.  ───────────────────────────────────────────────────────────────────────────
  14568.  
  14569.  Michael Geary, designer of SQLWindows, likes a lot of things about
  14570.  programming in Microsoft Windows.
  14571.  
  14572.  "To begin with," he says, "there's the user interface. One of the nice
  14573.  things about Windows is that the Windows style guide spells out an excellent
  14574.  user interface in great detail. That makes life easier for the programmer,
  14575.  and it also makes things much easier for the user. Once someone has learned
  14576.  one application, it's a lot easier to go learn another one."
  14577.  
  14578.  Geary said he appreciates the fact that Windows provides the tools to build
  14579.  its interface. "When I want to write a menu, I don't have to write code to
  14580.  say how menus work. All the basic user interface tidbits are built in," he
  14581.  said.
  14582.  
  14583.  
  14584.  Messages and Child Windows
  14585.  
  14586.  Geary, who had programmed extensively in both the Macintosh and the MS-
  14587.  DOS(R) environments before taking on SQLWindows (among his credits is the
  14588.  respected communications package Transend/PC), was particularly impressed
  14589.  by two aspects of Windows programming: messages and child windows.
  14590.  
  14591.  "I'm often asked to compare the Macintosh and Windows," he says. "From the
  14592.  user's point of view and the programmer's point of view, they're very
  14593.  similar. But I was really struck with the way Windows took the Mac's
  14594.  notion of events and generalized that into the message concept. Messages
  14595.  are like a superset of events. What you can do in Windows that you can't
  14596.  do on the Mac is make up your own messages and send them from one
  14597.  application to another. That's what makes DDE [Dynamic Data Exchange]
  14598.  possible; DDE is just a protocol for sending messages."
  14599.  
  14600.  As for child windows, there's basically only one kind of window on the
  14601.  Macintosh, Geary explains. Each window is essentially a peer of all the
  14602.  others, and once a program has built a window it's up to the programmer to
  14603.  manage everything that goes on inside it. The Macintosh offers such tools as
  14604.  push buttons, radio buttons, scroll bars, and so on, but the program has to
  14605.  manage the details of where they're placed and how they're used.
  14606.  
  14607.  In Windows, the common objects by means of which the user interacts with the
  14608.  application, such as the various components of a dialog box, are
  14609.  typically implemented as child windows. And because child windows behave
  14610.  essentially the same way parent windows do, a lot of things happen
  14611.  automatically that would have required explicit programming on the
  14612.  Macintosh.
  14613.  
  14614.  
  14615.  Subclassing
  14616.  
  14617.  "Another thing I like about the child window concept," adds Geary, "is that
  14618.  Windows supplies you with a number of predefined classes of child windows
  14619.  and then makes it easy for you to subclass them. You just take the
  14620.  predefined class and add on some behavior of your own by interposing your
  14621.  own window function in front of the one that comes with it, in effect
  14622.  filtering out the messages that you want to handle differently."
  14623.  
  14624.  Geary used this technique extensively in programming SQLWindows. Objects in
  14625.  the forms-layout editor window have to behave one way when the user of
  14626.  SQLWindows is developing an application and another way when the end user of
  14627.  that application is working with it. For example, the developer needs to be
  14628.  able to click on such objects as push buttons in order to size and position
  14629.  them; the end user wants them to behave like normal push buttons.
  14630.  
  14631.  "I was able to do all that split behavior just by using the subclassing
  14632.  technique," Geary explains. "I put my own window function ahead of the
  14633.  standard one. At design time I handle many of the messages differently; at
  14634.  run time, I just pass them through."
  14635.  
  14636.  Geary says he created a number of window classes from scratch, including a
  14637.  list window that handles columns as well as rows, somewhat like a
  14638.  spreadsheet, and can fetch data from a database on the fly (a "regular"
  14639.  Windows list window requires that all its data be in memory).
  14640.  
  14641.  "The nice thing about this," he elaborated, "is that my own window classes
  14642.  coexist with the built-in Windows classes, and there's not much special I
  14643.  have to do to make my own objects work side by side with pre-existing ones.
  14644.  The rest of my program treats them all the same; it doesn't care whether
  14645.  the window was provided by me or by Windows."
  14646.  
  14647.  
  14648.  Resource Management
  14649.  
  14650.  Other advantages to programming in Windows involve the management of
  14651.  memory and the keyboard. Because all code in Windows is relocatable and
  14652.  discardable, the programmer, according to Geary, is liberated from both
  14653.  memory fragmentation problems and the need to juggle the trade-offs
  14654.  associated with code overlays.
  14655.  
  14656.  "In programs that I've done in the past," he explains, "I've had to use
  14657.  overlays a lot. And it's always been a pain trying to decide, ╘How heavily
  14658.  do I overlay this? What's the target machine size? Whom do I optimize this
  14659.  for?' I've been through that process a number of times and it's awful. You
  14660.  can't satisfy everybody.
  14661.  
  14662.  "But in Windows, all you do is break up the program into a lot of code
  14663.  segments. Each code segment is an independent entity as far as being loaded
  14664.  into memory is concerned, and Windows just loads in as many as it can fit.
  14665.  When it runs out of room, it moves them around or discards them as needed.
  14666.  In other words, it optimizes for all the different machines at once.
  14667.  
  14668.  "You have to do a little extra work to take advantage of Windows' memory
  14669.  management, but it's much better than dealing with a fixed-overlay
  14670.  structure."
  14671.  
  14672.  As for keyboard management, Geary applauds the fact that Windows supplies
  14673.  the programmer with information that eliminates the need for direct
  14674.  programming of the hardware.
  14675.  
  14676.  "In Windows," he said, "keyboard handling is very simple. They send you
  14677.  messages for everything. With any keypress, you get WM_KEYDOWN. When any
  14678.  key is released, you get WM_KEYUP. You can distinguish between the first
  14679.  press of a key and presses that come from the auto repeat mechanism.
  14680.  
  14681.  "Basically, you can look at the keyboard at whatever level you like, without
  14682.  being an ill-behaved program. There's no incentive to go to the hardware,
  14683.  because they've provided all the stuff you want to do in the first place."
  14684.  
  14685.  Finally, Geary cites the advantages afforded to programmers and users
  14686.  alike by DDE and the Windows Clipboard. He believes the presence of a well-
  14687.  defined standard for interapplication communications will have a long-
  14688.  range impact on the types of software people write.
  14689.  
  14690.  "Philosophically, the Clipboard and DDE allow people to get away from
  14691.  building complex all-in-one applications and open up the possibility of
  14692.  getting back to building smaller programs that work effectively together."
  14693.  
  14694.  ████████████████████████████████████████████████████████████████████████████
  14695.  
  14696.  The Graphics Programming Interface: A Guide to OS/2 Presentation Spaces
  14697.  
  14698.  Charles Petzold
  14699.  
  14700.  Programmers experienced with Microsoft(R) Windows will discover that much of
  14701.  the OS/2 systems Presentation Manager is familiar territory. The windowing
  14702.  and user interface portion of the Presentation Manager is obviously derived
  14703.  from Windows, and many of the window messages are identical.
  14704.  
  14705.  However, the Graphics Programming Interface (GPI) is mostly new and
  14706.  offers some very significant enhancements over the Windows Graphics Device
  14707.  Interface (GDI). GPI is derived largely from IBM's Graphical Data Display
  14708.  Manager (GDDM) and 3270 Graphics Control Program (GCP) with some
  14709.  features, such as bitmaps and regions, derived from Windows. GPI also
  14710.  reflects influence from many other graphics interfaces, ranging from the
  14711.  Graphical Kernel System (GKS) to PostScript(R).
  14712.  
  14713.  One of the first hurdles in approaching GPI is dealing with the concept of
  14714.  the presentation space (PS). What makes it difficult is that the
  14715.  Presentation Manager supports three different types of presentation
  14716.  spaces, the "cached micro-PS," the "micro-PS," and the "normal-PS." Which
  14717.  type of presentation space you choose for your application depends on a
  14718.  variety of factors, some of which I'll discuss here.
  14719.  
  14720.  The subject becomes more interesting when you realize that the type of
  14721.  presentation space you choose determines to some degree how you structure
  14722.  a Presentation Manager application for drawing. I'll demonstrate this
  14723.  in three programs, CACHEDPS, MICROPS, and NORMALPS, each of which uses one
  14724.  of the three available types of presentation space.
  14725.  
  14726.  Each program displays the same simple graphics shown in Figure 1. The text
  14727.  string "Graphics Programming Interface" is displayed in the center of the
  14728.  window with dotted lines drawn from the corners of the text string to the
  14729.  corners of the window. Both the text and the dotted lines are displayed in
  14730.  red.
  14731.  
  14732.  
  14733.  GPI Drawing Functions
  14734.  
  14735.  Before we discuss the presentation space, let's take a quick look at
  14736.  the functions that these three programs use for drawing the text string
  14737.  and the dotted lines. The CACHEDPS.C program calls these GPI drawing
  14738.  functions in its ClientWndProc function during processing of the WM_PAINT
  14739.  message.
  14740.  
  14741.  Many of the GPI drawing functions require a point in x and y coordinates,
  14742.  which is provided to the function as a structure of type POINTL. The
  14743.  POINTL structure is defined in the OS2DEF.H header file like this:
  14744.  
  14745.    typedef struct _POINTL
  14746.    {
  14747.    LONG x ;
  14748.    LONG y ;
  14749.    }
  14750.    POINTL ;
  14751.  
  14752.  The LONG data type is simply a 32-bit unsigned long integer.
  14753.  
  14754.  To define a POINTL structure in a C program you can use
  14755.  
  14756.    struct _POINTL ptl ;
  14757.  
  14758.  or more simply:
  14759.  
  14760.    POINTL ptl ;
  14761.  
  14762.  By convention, the names of POINTL variables begin with the prefix ptl.
  14763.  
  14764.  (One big difference between GPI and some other high-level graphics
  14765.  languages is that GPI does not use floating point. This is for performance
  14766.  reasons──floating point calculations on personal computers are not yet fast
  14767.  enough for a highly interactive graphical system such as the Presentation
  14768.  Manager.)
  14769.  
  14770.  GPI includes the concept of a current position. To draw a line, the first
  14771.  step is to set the current position to the beginning of the line. This
  14772.  requires assigning coordinate values to the x and y fields of the POINTL
  14773.  structure and calling the GpiMove function:
  14774.  
  14775.    ptl.x = ... ;
  14776.    ptl.y = ... ;
  14777.    GpiMove (hps,&ptl) ;
  14778.  
  14779.  I'll discuss the hps parameter of GpiMove shortly. GpiMove does not draw
  14780.  anything; it merely sets the current position. To draw the line, set the
  14781.  fields of the structure to the end point of the line and call GpiLine:
  14782.  
  14783.    ptl.x = ... ;
  14784.    ptl.y = ... ;
  14785.    GpiLine (hps,&ptl) ;
  14786.  
  14787.  The GpiLine function also moves the current position to the indicated
  14788.  point.
  14789.  
  14790.  It certainly is a nuisance to set two fields of a POINTL structure before
  14791.  calling GpiMove or GpiLine. However, you'll find that the use of a
  14792.  structure to represent points offers a nice clean consistency in GPI
  14793.  function syntax. For example, the GpiQueryCurrentPosition function, which
  14794.  obtains the current position, has the same syntax as GpiMove:
  14795.  
  14796.    GpiQueryCurrentPosition (hps,&ptl) ;
  14797.  
  14798.  Upon returning from the function, the ptl structure is filled with the
  14799.  coordinates of the current position.
  14800.  
  14801.  The use of a structure for points also allows syntax consistency in those
  14802.  GPI functions that require multiple points. One such function is
  14803.  GpiPolyLine, which draws a series of lines beginning at the current
  14804.  position. You define an array of POINTL structures like this:
  14805.  
  14806.    POINTL aptl [15] ;
  14807.  
  14808.  The call to GpiPolyLine requires the number of points and the array name:
  14809.  
  14810.    GpiPolyLine (hps,15L, aptl) ;
  14811.  
  14812.  That GpiPolyLine call is almost equivalent to this code:
  14813.  
  14814.    for (i = 0 ; i < 15 ; i++)
  14815.    GpiLine (hps,aptl + i) ;
  14816.  
  14817.  The points you specify in GPI functions are in "world coordinates."
  14818.  Although GPI supports several transforms to convert world coordinates to
  14819.  pixels, I'll defer discussion of these transforms. By default, world
  14820.  coordinates are in units of pixels relative to the lower left corner of
  14821.  the window. Values on the x (horizontal) axis increase to the right;
  14822.  values on the y (vertical) axis increase going up.
  14823.  
  14824.    CACHEDPS.C uses the function GpiCharStringAt to display the text string:
  14825.    GpiCharStringAt (hps, &ptl,lTextLength,cText) ;
  14826.  
  14827.  Under default conditions, the baseline of the left side of the first
  14828.  character is positioned at the point given in the ptl structure. The cText
  14829.  parameter is a pointer to a character string or a character array, and
  14830.  lTextLength is the length of the string. For example, this code displays
  14831.  the text "Hello" at the point (10, 10):
  14832.  
  14833.    ptl.x = 10 ;
  14834.    ptl.y = 10 ;
  14835.    GpiCharStringAt (hps, &ptl,5L,"Hello") ;
  14836.  
  14837.  By default, the font that GPI uses is the normal system font, the same
  14838.  font that appears in Presentation Manager title bars, menus, and dialog
  14839.  boxes.
  14840.  
  14841.  
  14842.  GPI Attributes
  14843.  
  14844.  CACHEDPS.C calls two functions to set the attributes that GPI uses when
  14845.  drawing. Before drawing anything, the program calls GpiSetColor:
  14846.  
  14847.    GpiSetColor (hps,CLR_RED) ;
  14848.  
  14849.  This causes all text and lines displayed after this function call to be
  14850.  colored red. Before displaying the four lines, CACHEDPS.C calls another
  14851.  attribute function to set a dotted line type style:
  14852.  
  14853.    GpiSetLineType (hps, LINETYPE_DOT) ;
  14854.  
  14855.  GPI also has functions that obtain information from the system. One of
  14856.  the query functions used in CACHEDPS.C is GpiQueryTextBox. This function
  14857.  fills a POINTL array with the coordinate positions of the four corners of
  14858.  an imaginary box that would surround a particular character string if
  14859.  the string were displayed at the point (0, 0). The CACHEDPS program uses
  14860.  this information to draw the four lines.
  14861.  
  14862.  Near the beginning of WM_PAINT processing, CACHEDPS.C makes a call to
  14863.  GpiCreateLogColorTable. In the version of the Presentation Manager I used
  14864.  for this article, the default background color is black and the default
  14865.  foreground color is white. Thus, the GpiErase function erases the window
  14866.  to black. The call to GpiCreateLogColorTable in CACHEDPS reverses this so
  14867.  that the default background color is white and the foreground color is
  14868.  black. Whether this function will be necessary in the final version of the
  14869.  Presentation Manager is not clear as of this writing.
  14870.  
  14871.  The calls to GpiRestorePS, GpiSavePS, and GpiResetPS in CACHEDPS.C during
  14872.  the WM_PAINT message are temporary fixes to avoid some bugs in the
  14873.  version of the Presentation Manager I have. These functions should not
  14874.  be necessary when the Presentation Manager is in final shape.
  14875.  
  14876.  
  14877.  The PS and DC
  14878.  
  14879.  The first parameter to every GPI function in CACHEDPS.C is the variable
  14880.  named hps. This variable is defined as type HPS, a handle to a
  14881.  presentation space.
  14882.  
  14883.  Before a Presentation Manager program can draw on the display, it must
  14884.  either create a presentation space or use a presentation space that has
  14885.  already been created by the Presentation Manager. How you do this depends
  14886.  on the type of presentation space you decide to use.
  14887.  
  14888.  The presentation space is basically a data structure internal to the
  14889.  Presentation Manager. All the attributes of the presentation space──the
  14890.  current position, the current background and foreground colors, the
  14891.  current line style type, transforms, and others──are stored here. For the
  14892.  normal-PS, the presentation space can also store collections of GPI
  14893.  function calls.
  14894.  
  14895.  A Windows programmer might at first assume that the presentation space is
  14896.  equivalent to the Windows display context (DC), but they are really two
  14897.  different things. In fact, the Presentation Manager includes the
  14898.  concept of a display context.
  14899.  
  14900.  In the Presentation Manager, the display context usually describes a
  14901.  physical output device, such as the video display, printer, or plotter. A
  14902.  display context can also be a block of memory that is used as if it were
  14903.  an output device; it can also be a metafile. You can think of the display
  14904.  context as comprising the output device and its device driver.
  14905.  
  14906.  A presentation space does not itself imply any type of output device.
  14907.  Instead, the presentation space must be associated with a particular
  14908.  display context. The reason for the separation of the presentation space
  14909.  and the display context will be evident later. The conceptual
  14910.  relationship between an application program, the presentation space, and
  14911.  the display context is shown in Figure 2.
  14912.  
  14913.  Of the three different types of presentation spaces supported by the
  14914.  Presentation Manager, normal-PS, sometimes also called the full-PS, is the
  14915.  most versatile, with micro-PS second and cached micro-PS being the least
  14916.  versatile.
  14917.  
  14918.  To use a normal-PS or micro-PS, a program must create the presentation
  14919.  space by calling the function GpiCreatePS. Strictly speaking, these are
  14920.  the only two types of presentation space supported by GPI. The cached
  14921.  micro-PS is provided courtesy of the component of the Presentation
  14922.  Manager concerned with the windowing and user interface functions,
  14923.  sometimes called the "Win" or "User" component. The Presentation Manager
  14924.  creates the micro-PS for you.
  14925.  
  14926.  
  14927.  The Cached Micro-PS
  14928.  
  14929.  The CACHEDPS program shown in Figure 3 uses a cached micro-PS. (For more
  14930.  information on cached micro-PS see Microsoft(R) Operating System/2 Software
  14931.  Development Kit, Presentation Manager Specification, Vol. 1, p. 210) A
  14932.  program can obtain a handle to a cached micro-PS by calling WinBeginPaint
  14933.  or WinGetPS. CACHEDPS.C uses both of these functions.
  14934.  
  14935.  You can call the WinGetPS function while processing any window message.
  14936.  After you're done with the presentation space you release it by calling
  14937.  WinReleasePS:
  14938.  
  14939.    hps = WinGetPS (hwnd) ;
  14940.              ∙
  14941.              ∙
  14942.              ∙
  14943.    < use GPI functions >
  14944.              ∙
  14945.              ∙
  14946.              ∙
  14947.    WinReleasePS (hps) ;
  14948.  
  14949.  CACHEDPS calls WinGetPS during the WM_CREATE message. The only GPI
  14950.  function the program calls at that time is GpiQueryTextBox. CACHEDPS needs
  14951.  to obtain the dimensions of the character box only once because the height
  14952.  and width of the text string will not change while the program is
  14953.  running.
  14954.  
  14955.  The functions WinGetPS and WinReleasePS must be called as a pair while
  14956.  processing a single message. Do not call WinGetPS while processing one
  14957.  message and WinReleasePS while processing another.
  14958.  
  14959.  During processing of a WM_PAINT message, a program calls WinBeginPaint to
  14960.  obtain a handle to a cached micro-PS and WinEndPaint to release the
  14961.  handle:
  14962.  
  14963.    hps = WinBeginPaint (hwnd, NULL,NULL) ;
  14964.              ∙
  14965.              ∙
  14966.              ∙
  14967.    < use GPI functions >
  14968.              ∙
  14969.              ∙
  14970.              ∙
  14971.    WinEndPaint (hps) ;
  14972.  
  14973.  A cached micro-PS has several important characteristics. First, the cached
  14974.  micro-PS is always associated with the video display, or more precisely,
  14975.  with a window on the video display. That makes the cached micro-PS easy to
  14976.  use (WinGetPS requires only one parameter) but also limits it to the screen.
  14977.  
  14978.  Second, all attributes of the presentation space are set to their default
  14979.  values when the handle is obtained by calling WinGetPS or WinBeginPaint.
  14980.  This is why CACHEDPS.C sets the color to red and the line style type to
  14981.  dotted when it processes the WM_PAINT message.
  14982.  
  14983.  Finally, when using the cached micro-PS a program usually draws in units
  14984.  of pixels. You can change that with a few calls to certain GPI functions,
  14985.  but it is not quite as easy as when a program creates its own
  14986.  presentation space by calling GpiCreatePS.
  14987.  
  14988.  Windows programmers will note that CACHEDPS is structured much like a
  14989.  Windows program. If you're converting a program from Windows to the
  14990.  Presentation Manager, you may want to use a cached micro-PS to ease the
  14991.  conversion.
  14992.  
  14993.  
  14994.  The WM_PAINT Message
  14995.  
  14996.  CACHEDPS does all of its drawing while processing the WM_PAINT message.
  14997.  Handling the WM_PAINT message correctly is an important structural
  14998.  consideration in both Windows programs and Presentation Manager
  14999.  programs.
  15000.  
  15001.  A window procedure receives a WM_PAINT message whenever an area of the
  15002.  window has become invalid, meaning that the area no longer contains what
  15003.  the program originally drew there. If a portion of a window has been
  15004.  covered and then becomes uncovered, the Presentation Manager will post a
  15005.  WM_PAINT message to that window. A window procedure can also receive a
  15006.  WM_PAINT message when the window has been changed in size.
  15007.  
  15008.  Although a Presentation Manager program can draw on a window at almost any
  15009.  time, the program must be able to update the entire window on receipt of a
  15010.  WM_PAINT message. Programmers often satisfy this requirement by doing all
  15011.  window painting during the WM_PAINT message and none at any other time.
  15012.  As you'll see, dealing with WM_PAINT messages becomes somewhat simpler
  15013.  with the normal-PS.
  15014.  
  15015.  
  15016.  The Micro-PS
  15017.  
  15018.  Because the cached micro-PS is always associated with a window on the
  15019.  video display, you cannot use a cached micro-PS for drawing on another
  15020.  output device, such as a printer or a plotter. To use a printer or
  15021.  plotter you must create either a micro-PS or normal-PS. A program can
  15022.  also create a micro-PS for drawing on the program's window. The MICROPS
  15023.  program in Figure 4 shows how this is done.
  15024.  
  15025.  MICROPS.C creates the presentation space by calling GpiCreatePS while
  15026.  processing the WM_CREATE message in ClientWndProc. GPIT_MICRO in the
  15027.  GpiCreatePS function tells GPI to create a micro-PS. GpiCreatePS also
  15028.  requires a handle to a display context with which the presentation space
  15029.  will be associated. For a video display window, this display context
  15030.  handle is obtained from WinOpenWindowDC:
  15031.  
  15032.    hdc = WinOpenWindowDC (hwnd) ;
  15033.  
  15034.  The presentation space includes an imaginary drawing surface called the
  15035.  "presentation page." (I promise that this is the last term I'll be
  15036.  presenting here.) You define the size and units of the presentation page
  15037.  in the GpiCreatePS function. You set the size of the page by the SIZEL
  15038.  structure. Setting the cx and cy fields to 0 makes the page size the same
  15039.  as the size of the entire video display. The PU_PELS identifier indicates
  15040.  that the coordinates of the page are in units of pixels. You can instead
  15041.  use one of the identifiers shown in Figure 5 for units other than pixels.
  15042.  
  15043.  Using a pixel-coordinate page is often the best approach for simple
  15044.  graphics output. Information obtained from the Presentation Manager
  15045.  windowing system, such as the size of the window obtained during the
  15046.  WM_SIZE message, is always in units of pixels regardless of the
  15047.  presentation page size. You would have to convert those coordinates and
  15048.  sizes to the page units using GpiConvert.
  15049.  
  15050.  The presentation space handle obtained from GpiCreatePS is valid until the
  15051.  presentation space is destroyed by a call to GpiDestroyPS. MICROPS
  15052.  destroys the presentation space while it's processing the WM_DESTROY
  15053.  message, which is the last message the window procedure receives. Until
  15054.  that time, the hps value must be used during all WM_PAINT and WM_SIZE
  15055.  messages, so it is stored in a static variable.
  15056.  
  15057.  Note that MICROPS sets the color and line style type during WM_CREATE
  15058.  processing rather than during WM_PAINT processing. The micro-PS exists
  15059.  until it is explicitly destroyed so any GPI attributes that a program sets
  15060.  remain in the presentation space until they are changed. In contrast, a
  15061.  cached micro-PS is reset to default attributes each time a program
  15062.  obtains it.
  15063.  
  15064.  The syntax of the function WinBeginPaint for a micro-PS is a little
  15065.  different from that for a cached micro-PS. Rather than use WinBeginPaint
  15066.  to obtain the presentation space handle, the existing handle is passed to
  15067.  the function as the second parameter. (In the version of the Presentation
  15068.  Manager that I used for this article, WinBeginPaint and WinEndPaint were not
  15069.  working correctly; that's why they are shown in MICROPS.C between comment
  15070.  delimiters.)
  15071.  
  15072.  Windows programmers, watch out! You'll find it natural to do something
  15073.  like this during the WM_PAINT message when using a cached micro-PS:
  15074.  
  15075.    hps = WinBeginPaint (hwnd, NULL,NULL) ;
  15076.              ∙
  15077.              ∙
  15078.              ∙
  15079.    < draw using default colors >
  15080.              ∙
  15081.              ∙
  15082.              ∙
  15083.    GpiSetColor (hps,CLR_RED) ;
  15084.              ∙
  15085.              ∙
  15086.              ∙
  15087.    < draw using red >
  15088.              ∙
  15089.              ∙
  15090.              ∙
  15091.    WinEndPaint (hps) ;
  15092.  
  15093.  However, if you switch to a micro-PS, you might first alter the code like
  15094.  this:
  15095.  
  15096.    WinBeginPaint (hwnd,hps, NULL) ;
  15097.              ∙
  15098.              ∙
  15099.              ∙
  15100.    < draw using default colors >
  15101.              ∙
  15102.              ∙
  15103.              ∙
  15104.    GpiSetColor (hps,CLR_RED) ;
  15105.              ∙
  15106.              ∙
  15107.              ∙
  15108.    < draw using red >
  15109.              ∙
  15110.              ∙
  15111.              ∙
  15112.    WinEndPaint (hps) ;
  15113.  
  15114.  This will work fine during the first WM_PAINT message but not during
  15115.  subsequent messages because the color will still be set to red. Remember
  15116.  that the micro-PS retains all GPI attributes that you change until you
  15117.  change them again. You can avoid such problems by using GpiSavePS and
  15118.  GpiRestorePS to save and restore the GPI state:
  15119.  
  15120.    WinBeginPaint (hwnd,hps, NULL) ;
  15121.              ∙
  15122.              ∙
  15123.              ∙
  15124.    < draw using default
  15125.    colors >
  15126.              ∙
  15127.              ∙
  15128.              ∙
  15129.    GpiSavePS (hps) ;
  15130.    GpiSetColor (hps,CLR_RED) ;
  15131.              ∙
  15132.              ∙
  15133.              ∙
  15134.    < draw using red >
  15135.              ∙
  15136.              ∙
  15137.              ∙
  15138.    GpiRestorePS (hps,-1L) ;
  15139.    WinEndPaint (hps) ;
  15140.  
  15141.  Or you can explicitly set the color back to the default value at the end
  15142.  of WM_PAINT processing:
  15143.  
  15144.    WinBeginPaint (hwnd, hps, NULL) ;
  15145.              ∙
  15146.              ∙
  15147.              ∙
  15148.    < draw using default colors >
  15149.              ∙
  15150.              ∙
  15151.              ∙
  15152.    GpiSetColor (hps,CLR_RED) ;
  15153.              ∙
  15154.              ∙
  15155.              ∙
  15156.    < draw using red >
  15157.              ∙
  15158.              ∙
  15159.              ∙
  15160.    GpiSetColor (hps, CLR_DEFAULT) ;
  15161.    WinEndPaint (hps) ;
  15162.  
  15163.  Comparing CACHEDPS.C and MICROPS.C listings reveals only a subtle change
  15164.  in structure-two GPI attribute functions called during the WM_PAINT message
  15165.  in CACHEDPS are handled in MICROPS during the WM_CREATE message. The real
  15166.  change in structure occurs when you move up to the normal-PS.
  15167.  
  15168.  
  15169.  The Normal-PS
  15170.  
  15171.  The micro-PS gives a program access to only a subset of the GPI
  15172.  functions. Although it is a very substantial subset and includes all the
  15173.  basics, it excludes all functions that pertain to GPI "segments"; these
  15174.  are supported only by the normal-PS.
  15175.  
  15176.  A segment (not to be confused with a memory segment in 80286
  15177.  microprocessor architecture) is a stored collection of graphics drawing
  15178.  and attribute commands. You open a segment, you call a bunch of GPI
  15179.  functions, and then you close the segment. All the functions are stored
  15180.  in the segment. You can then cause these commands to be drawn on the
  15181.  output device by issuing one of several functions such as GpiDrawChain.
  15182.  The NORMALPS program shown in Figure 6 gives you a little taste of
  15183.  segments.
  15184.  
  15185.  The graphics output of NORMALPS depends only on the size of the window.
  15186.  So, during the WM_SIZE message, a segment is created by a call to
  15187.  GpiOpenSegment. The attribute and drawing commands are called just as they
  15188.  were in the two earlier programs. But these graphics orders are not
  15189.  displayed just yet; they are instead saved in the segment. During the
  15190.  WM_PAINT message, the segment is displayed by calling GpiDrawChain.
  15191.  Obviously this simplifies WM_PAINT processing considerably.
  15192.  
  15193.  You can create many segments in your program; they are saved as part of
  15194.  the presentation space. A segment can be either chained or unchained.
  15195.  The root chain consists of all chained segments; GpiDrawChain draws all
  15196.  the segments in the root chain. Unchained segments can be called from
  15197.  other segments. When an unchained segment is called, the coordinate
  15198.  positions specified in all the drawing commands in the segment can be
  15199.  transformed with translation, scaling, or rotation, which is useful for
  15200.  graphical modeling.
  15201.  
  15202.  You can edit segments, change the order in which segments in the root
  15203.  chain are drawn, and test whether an object drawn in a segment is within a
  15204.  specified radius of a particular point. This is very handy for mouse hit-
  15205.  testing.
  15206.  
  15207.  Besides the support of segments, the normal-PS has another advantage over
  15208.  the micro-PS. It is the only presentation space that can be
  15209.  reassociated with another display context; that is, you can disassociate
  15210.  the presentation space from the video display and then associate it with
  15211.  the printer. Because the segments are part of the presentation space,
  15212.  you need not recreate them. The segments are ready to be drawn on the
  15213.  printer.
  15214.  
  15215.  Of course, the use of segments is overkill in NORMALPS, as it would be in
  15216.  many other small Presentation Manager programs. The basic rule is to use
  15217.  the simplest type of presentation space that meets your needs. If you
  15218.  can do everything you need to do using the cached micro-PS, use that.
  15219.  Don't take a full set of luggage on an overnight trip.
  15220.  
  15221.  
  15222.  Figure 2:  The relatonship between an application program, the presentation
  15223.             space, and the display context.
  15224.  
  15225.           ┌─────────────────────────────────────────────────────────────┐
  15226.           │                                                             │█
  15227.           │         ╔══════════════════════════════════════════╗        │█
  15228.           │         ║            Application Program           ║        │█
  15229.           │         ╚════════════════════╤═════════════════════╝        │█
  15230.           │                         draws to the                        │█
  15231.           │                                                            │█
  15232.           │         ╔══════════════════════════════════════════╗        │█
  15233.           │         ║             Presentation Space           ║        │█
  15234.           │         ╚════════════════════╤═════════════════════╝        │█
  15235.           │                   which is associated with a                │█
  15236.           │                                                            │█
  15237.           │         ╔══════════════════════════════════════════╗        │█
  15238.           │         ║              Display Context             ║        │█
  15239.           │         ╚════════════════════╤═════════════════════╝        │█
  15240.           │           which causes output to be displayed on a          │█
  15241.           │                                                            │█
  15242.           │         ╔══════════════════════════════════════════╗        │█
  15243.           │         ║          Physical Output Device          ║        │█
  15244.           │         ╚══════════════════════════════════════════╝        │█
  15245.           └─────────────────────────────────────────────────────────────┘█
  15246.             ██████████████████████████████████████████████████████████████
  15247.  
  15248.  
  15249.  Figure 3:
  15250.  
  15251.  CACHEDPS Make File
  15252.  
  15253.  cachedps.obj : cachedps.c
  15254.       cl -c -G2sw -W2 -Zp cachedps.c
  15255.  
  15256.  cachedps.exe : cachedps.obj cachedps.def
  15257.       link cachedps, /align:16, NUL, os2, cachedps
  15258.  
  15259.  CACHEDPS.DEF Module Definition File
  15260.  
  15261.  NAME           CACHEDPS
  15262.  DESCRIPTION   'Demonstrates Cached Micro-PS(C) Charles Petzold, 1988'
  15263.  PROTMODE
  15264.  HEAPSIZE       1024
  15265.  STACKSIZE      8192
  15266.  EXPORTS        ClientWndProc
  15267.  
  15268.  CACHEDPS.C-Demonstrates Cached Micro-PS
  15269.  
  15270.  #define INCL_GPI
  15271.  
  15272.  #include <os2.h>
  15273.  #include <stddef.h>
  15274.  
  15275.  MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  15276.  
  15277.  main ()
  15278.       {
  15279.      static CHAR szClientClass [] = "CachedPS" ;
  15280.      HAB    hab ;
  15281.      HMQ    hmq ;
  15282.      HWND   hwndFrame, hwndClient ;
  15283.      QMSG   qmsg ;
  15284.  
  15285.       hab = WinInitialize (0) ;
  15286.       hmq = WinCreateMsgQueue (hab, 0) ;
  15287.       WinRegisterClass (hab, szClientClass, ClientWndProc,
  15288.                                             CS_SIZEREDRAW, 0) ;
  15289.  
  15290.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  15291.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  15292.                                 | FS_SYSMENU    | FS_MINMAX,
  15293.                    szClientClass, "Cached Micro-PS",
  15294.                      0L, NULL, 0, &hwndClient) ;
  15295.  
  15296.       while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  15297.            WinDispatchMsg (hab, &qmsg) ;
  15298.  
  15299.       WinDestroyWindow (hwndFrame) ;
  15300.       WinDestroyMsgQueue (hmq) ;
  15301.       WinTerminate (hab) ;
  15302.  
  15303.       return 0 ;
  15304.       }
  15305.  
  15306.  MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
  15307.       HWND          hwnd ;
  15308.       USHORT        msg ;
  15309.       MPARAM        mp1 ;
  15310.       MPARAM        mp2 ;
  15311.       {
  15312.       static CHAR   szText []  = "Graphics Programming Interface" ;
  15313.       static LONG   lTextLength    = sizeof szText - 1L ;
  15314.       static LONG   alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
  15315.                                        CLR_NEUTRAL,    RGB_BLACK } ;
  15316.       static POINTL ptlTextStart, aptlLineStart [4],
  15317.                     aptlTextBox [TXTBOX_COUNT] ;
  15318.       static SHORT  cxClient, cyClient ;
  15319.       HPS           hps ;
  15320.       POINTL        ptl ;
  15321.       SHORT         sIndex ;
  15322.  
  15323.       switch (msg)
  15324.            {
  15325.            case WM_CREATE:
  15326.                 hps = WinGetPS (hwnd) ;
  15327.  
  15328.                 GpiQueryTextBox (hps, lTextLength, szText,
  15329.                                  TXTBOX_COUNT, aptlTextBox) ;
  15330.  
  15331.                 WinReleasePS (hps) ;
  15332.                 break ;
  15333.  
  15334.            case WM_SIZE:
  15335.                 cxClient = LOUSHORT (mp2) ;
  15336.                 cyClient = HIUSHORT (mp2) ;
  15337.  
  15338.                 ptlTextStart.x = (cxClient -
  15339.                               aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
  15340.                               aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
  15341.  
  15342.                 ptlTextStart.y = (cyClient -
  15343.                               aptlTextBox [TXTBOX_TOPLEFT].y -
  15344.                               aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
  15345.  
  15346.                 for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
  15347.                      {
  15348.                      aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
  15349.                      aptlLineStart [sIndex].x += ptlTextStart.x ;
  15350.                      aptlLineStart [sIndex].y += ptlTextStart.y ;
  15351.                      }
  15352.                 break ;
  15353.  
  15354.            case WM_PAINT:
  15355.                 hps = WinBeginPaint (hwnd, NULL, NULL) ;
  15356.  
  15357.                 GpiSavePS (hps) ;                       /* temp fix */
  15358.                 GpiResetPS (hps, GRES_ATTRS) ;          /* temp fix */
  15359.                 GpiCreateLogColorTable (hps, LCOL_RESET,
  15360.                    LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
  15361.  
  15362.                 GpiErase (hps) ;
  15363.  
  15364.                 GpiSetColor (hps, CLR_RED) ;
  15365.  
  15366.                 GpiCharStringAt (hps, &ptlTextStart,
  15367.                                     lTextLength, szText) ;
  15368.  
  15369.                 GpiSetLineType (hps, LINETYPE_DOT) ;
  15370.  
  15371.                 GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMLEFT) ;
  15372.                 ptl.x = 0 ;
  15373.                 ptl.y = 0 ;
  15374.                 GpiLine (hps, &ptl) ;
  15375.  
  15376.                 GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMRIGHT) ;
  15377.                 ptl.x = cxClient ;
  15378.                 GpiLine (hps, &ptl) ;
  15379.  
  15380.                 GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
  15381.                 ptl.y = cyClient ;
  15382.                 GpiLine (hps, &ptl) ;
  15383.  
  15384.                 GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
  15385.                 ptl.x = 0 ;
  15386.                 GpiLine (hps, &ptl) ;
  15387.  
  15388.                 GpiRestorePS (hps, -1L) ;     /* temp fix */
  15389.  
  15390.                 WinEndPaint (hps) ;
  15391.                 break ;
  15392.  
  15393.            default:
  15394.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  15395.            }
  15396.       return FALSE ;
  15397.       }
  15398.  
  15399.  
  15400.  Figure 4:
  15401.  
  15402.  MICROPS Make File
  15403.  
  15404.  microps.obj : microps.c
  15405.       cl -c -G2sw -W2 -Zp microps.c
  15406.  
  15407.  microps.exe : microps.obj microps.def
  15408.       link microps, /align:16, NUL, os2, microps
  15409.  
  15410.  MICROPS.DEF Module Definition File
  15411.  
  15412.  NAME           MICROPS
  15413.  DESCRIPTION   'Demonstrates Micro-PS(C) Charles Petzold, 1988'
  15414.  PROTMODE
  15415.  HEAPSIZE       1024
  15416.  STACKSIZE      8192
  15417.  EXPORTS        ClientWndProc
  15418.  
  15419.  MICROPS.C-Demonstrates Micro-PS
  15420.  
  15421.  #define INCL_WIN
  15422.  #define INCL_GPI
  15423.  
  15424.  #include <os2.h>
  15425.  #include <stddef.h>
  15426.  
  15427.  MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  15428.  
  15429.  HAB  hab ;
  15430.  
  15431.  main ()
  15432.      {
  15433.      static CHAR szClientClass [] = "MicroPS" ;
  15434.      HMQ    hmq ;
  15435.      HWND   hwndFrame, hwndClient ;
  15436.      QMSG   qmsg ;
  15437.  
  15438.      hab = WinInitialize (0) ;
  15439.      hmq = WinCreateMsgQueue (hab, 0) ;
  15440.      WinRegisterClass (hab, szClientClass, ClientWndProc,
  15441.                                            CS_SIZEREDRAW, 0) ;
  15442.  
  15443.      hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  15444.                     WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  15445.                                | FS_SYSMENU    | FS_MINMAX,
  15446.                   szClientClass, "Micro-PS",
  15447.                     0L, NULL, 0, &hwndClient) ;
  15448.  
  15449.      while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  15450.           WinDispatchMsg (hab, &qmsg) ;
  15451.  
  15452.      WinDestroyWindow (hwndFrame) ;
  15453.      WinDestroyMsgQueue (hmq) ;
  15454.      WinTerminate (hab) ;
  15455.  
  15456.      return 0 ;
  15457.      }
  15458.  
  15459.  MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
  15460.      HWND          hwnd ;
  15461.      USHORT        msg ;
  15462.      MPARAM        mp1 ;
  15463.      MPARAM        mp2 ;
  15464.      {
  15465.      static CHAR   szText []  = "Graphics Programming Interface" ;
  15466.      static HPS    hps ;
  15467.      static LONG   lTextLength   = sizeof szText - 1L ;
  15468.      static LONG   alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
  15469.                                       CLR_NEUTRAL,    RGB_BLACK } ;
  15470.      static POINTL ptlTextStart, aptlLineStart [4],
  15471.                    aptlTextBox [TXTBOX_COUNT] ;
  15472.      static SHORT  cxClient, cyClient ;
  15473.      HDC           hdc ;
  15474.      POINTL        ptl ;
  15475.      SHORT         sIndex ;
  15476.      SIZEL         sizl ;
  15477.  
  15478.      switch (msg)
  15479.           {
  15480.           case WM_CREATE:
  15481.                hdc = WinOpenWindowDC (hwnd) ;
  15482.  
  15483.                sizl.cx = 0 ;
  15484.                sizl.cy = 0 ;
  15485.  
  15486.                hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS |
  15487.                                    GPIF_DEFAULT | GPIT_MICRO |
  15488.                                    GPIM_NORMAL  | GPIA_ASSOC) ;
  15489.  
  15490.                GpiCreateLogColorTable (hps, LCOL_RESET,
  15491.                    LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
  15492.  
  15493.                GpiQueryTextBox (hps, lTextLength, szText,
  15494.                                 TXTBOX_COUNT, aptlTextBox) ;
  15495.  
  15496.                GpiSetColor (hps, CLR_RED) ;
  15497.                GpiSetLineType (hps, LINETYPE_DOT) ;
  15498.                break ;
  15499.  
  15500.           case WM_SIZE:
  15501.                cxClient = LOUSHORT (mp2) ;
  15502.                cyClient = HIUSHORT (mp2) ;
  15503.  
  15504.                ptlTextStart.x = (cxClient -
  15505.                               aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
  15506.                               aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
  15507.  
  15508.                ptlTextStart.y = (cyClient -
  15509.                               aptlTextBox [TXTBOX_TOPLEFT].y -
  15510.                               aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
  15511.  
  15512.                for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
  15513.                     {
  15514.                     aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
  15515.                     aptlLineStart [sIndex].x += ptlTextStart.x ;
  15516.                     aptlLineStart [sIndex].y += ptlTextStart.y ;
  15517.                     }
  15518.  
  15519.                break ;
  15520.  
  15521.           case WM_PAINT:
  15522.                /* BeginPaint (hwnd, hps, NULL) ; */
  15523.  
  15524.                GpiErase (hps) ;
  15525.  
  15526.                GpiCharStringAt (hps, &ptlTextStart,
  15527.                                    lTextLength, szText) ;
  15528.  
  15529.                GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMLEFT) ;
  15530.                ptl.x = 0 ;
  15531.                ptl.y = 0 ;
  15532.                GpiLine (hps, &ptl) ;
  15533.  
  15534.                GpiMove (hps, aptlLineStart + TXTBOX_BOTTOMRIGHT) ;
  15535.                ptl.x = cxClient ;
  15536.                GpiLine (hps, &ptl) ;
  15537.  
  15538.                GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
  15539.                ptl.y = cyClient ;
  15540.                GpiLine (hps, &ptl) ;
  15541.  
  15542.                GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
  15543.                ptl.x = 0 ;
  15544.                GpiLine (hps, &ptl) ;
  15545.  
  15546.                /* EndPaint (hps) ; */
  15547.  
  15548.                WinValidateRect (hwnd, NULL, FALSE) ; /* temp fix */
  15549.                break ;
  15550.  
  15551.           case WM_DESTROY:
  15552.                GpiDestroyPS (hps) ;
  15553.                break ;
  15554.  
  15555.           default:
  15556.                return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  15557.           }
  15558.      return FALSE ;
  15559.      }
  15560.  
  15561.  
  15562.  Figure 5:  The identifiers used in the GpiCreatePS function to set the units
  15563.             of the presentation page.
  15564.  
  15565.  Page Units
  15566.  Identifier        Units
  15567.  
  15568.  PU_ARBITRARY      Pixels (with adjustment)
  15569.  PU_PELS           Pixels
  15570.  PU_LOMETRIC       0.1 millimeter
  15571.  PU_HIMETRIC       0.01 millimeter
  15572.  PU_LOENGLISH      0.01 inch
  15573.  PU_HIENGLISH      0.001 inch
  15574.  PU_TWIPS          1/1440 inch
  15575.  
  15576.  
  15577.  Figure 6:
  15578.  
  15579.  NORMALPS Make File
  15580.  
  15581.  normalps.obj : normalps.c
  15582.       cl -c -G2sw -W2 -Zp normalps.c
  15583.  
  15584.  normalps.exe : normalps.obj normalps.def
  15585.       link normalps, /align:16, NUL, os2, normalps
  15586.  
  15587.  NORMALPS.DEF Module Definition File
  15588.  
  15589.  NAME           NORMALPS
  15590.  DESCRIPTION   'Demonstrates Normal-PS(C) Charles Petzold, 1988'
  15591.  PROTMODE
  15592.  HEAPSIZE       1024
  15593.  STACKSIZE      8192
  15594.  EXPORTS       ClientWndProc
  15595.  
  15596.  NORMALPS.C-Demonstrates Normal-PS
  15597.  
  15598.  #define INCL_WIN
  15599.  #define INCL_GPI
  15600.  
  15601.  #include <os2.h>
  15602.  #include <stddef.h>
  15603.  
  15604.  MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  15605.  
  15606.  HAB  hab ;
  15607.  
  15608.  main ()
  15609.       {
  15610.       static CHAR szClientClass [] = "NormalPS" ;
  15611.       HMQ    hmq ;
  15612.       HWND   hwndFrame, hwndClient ;
  15613.       QMSG   qmsg ;
  15614.  
  15615.       hab = WinInitialize (0) ;
  15616.       hmq = WinCreateMsgQueue (hab, 0) ;
  15617.       WinRegisterClass (hab, szClientClass, ClientWndProc,
  15618.                                             CS_SIZEREDRAW, 0) ;
  15619.  
  15620.       hwndFrame = WinCreateStdWindow (HWND_DESKTOP,
  15621.                      WS_VISIBLE | FS_SIZEBORDER | FS_TITLEBAR
  15622.                                 | FS_SYSMENU    | FS_MINMAX,
  15623.                    szClientClass, "Normal-PS",
  15624.                      0L, NULL, 0, &hwndClient) ;
  15625.  
  15626.     while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  15627.            WinDispatchMsg (hab, &qmsg) ;
  15628.  
  15629.     WinDestroyWindow (hwndFrame) ;
  15630.     WinDestroyMsgQueue (hmq) ;
  15631.     WinTerminate (hab) ;
  15632.  
  15633.       return 0 ;
  15634.       }
  15635.  
  15636.  MRESULT EXPENTRY ClientWndProc (hwnd, msg, mp1, mp2)
  15637.       HWND          hwnd ;
  15638.       USHORT        msg ;
  15639.       MPARAM        mp1 ;
  15640.       MPARAM        mp2 ;
  15641.       {
  15642.       static CHAR   szText []      = "Graphics Programming Interface" ;
  15643.       static HPS    hps ;
  15644.       static LONG   lSegmentName   = 1 ;
  15645.       static LONG   lTextLength    = sizeof szText - 1L ;
  15646.       static LONG   alColorData [] = { CLR_BACKGROUND, RGB_WHITE,
  15647.                                        CLR_NEUTRAL,    RGB_BLACK } ;
  15648.       static POINTL aptlTextBox [TXTBOX_COUNT] ;
  15649.       HDC           hdc ;
  15650.       POINTL        ptl, ptlTextStart, aptlLineStart [4] ;
  15651.       SHORT         cxClient, cyClient, sIndex ;
  15652.       SIZEL         sizl ;
  15653.  
  15654.       switch (msg)
  15655.            {
  15656.            case WM_CREATE:
  15657.                 hdc = WinOpenWindowDC (hwnd) ;
  15658.  
  15659.                 sizl.cx = 0 ;
  15660.                 sizl.cy = 0 ;
  15661.  
  15662.                 hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    |
  15663.                                     GPIF_DEFAULT | GPIT_NORMAL |
  15664.                                     GPIM_NORMAL  | GPIA_ASSOC) ;
  15665.  
  15666.                 GpiCreateLogColorTable (hps, LCOL_RESET,
  15667.                    LCOLF_INDRGB, 0L, 4L, alColorData) ; /* temp fix */
  15668.  
  15669.                 GpiQueryTextBox (hps, lTextLength, szText,
  15670.                                  TXTBOX_COUNT, aptlTextBox) ;
  15671.  
  15672.                 GpiSetDrawControl (hps, DCTL_ERASE, DCTL_ON) ;
  15673.                 GpiSetDrawingMode (hps, DM_RETAIN) ;
  15674.                 break ;
  15675.  
  15676.            case WM_SIZE:
  15677.                 cxClient = LOUSHORT (mp2) ;
  15678.                 cyClient = HIUSHORT (mp2) ;
  15679.  
  15680.                 ptlTextStart.x = (cxClient -
  15681.                               aptlTextBox [TXTBOX_BOTTOMRIGHT].x -
  15682.                               aptlTextBox [TXTBOX_BOTTOMLEFT].x) / 2 ;
  15683.  
  15684.                 ptlTextStart.y = (cyClient -
  15685.                               aptlTextBox [TXTBOX_TOPLEFT].y -
  15686.                               aptlTextBox [TXTBOX_BOTTOMLEFT].y) / 2 ;
  15687.  
  15688.                 for (sIndex = 0 ; sIndex < 4 ; sIndex ++)
  15689.                      {
  15690.                      aptlLineStart [sIndex] = aptlTextBox [sIndex] ;
  15691.                      aptlLineStart [sIndex].x += ptlTextStart.x ;
  15692.                      aptlLineStart [sIndex].y += ptlTextStart.y ;
  15693.                      }
  15694.  
  15695.                 GpiDeleteSegment (hps, lSegmentName) ;
  15696.  
  15697.                 GpiOpenSegment (hps, lSegmentName) ;
  15698.                      {
  15699.                      GpiSetColor (hps, CLR_RED) ;
  15700.  
  15701.                      GpiCharStringAt (hps, &ptlTextStart,
  15702.                                       lTextLength, szText) ;
  15703.  
  15704.                      GpiSetLineType (hps, LINETYPE_DOT) ;
  15705.  
  15706.                      GpiMove (hps,aptlLineStart + TXTBOX_BOTTOMLEFT) ;
  15707.                      ptl.x = 0 ;
  15708.                      ptl.y = 0 ;
  15709.                      GpiLine (hps, &ptl) ;
  15710.  
  15711.                      GpiMove (hps,aptlLineStart + TXTBOX_BOTTOMRIGHT);
  15712.                      ptl.x = cxClient ;
  15713.                      GpiLine (hps, &ptl) ;
  15714.  
  15715.                      GpiMove (hps, aptlLineStart + TXTBOX_TOPRIGHT) ;
  15716.                      ptl.y = cyClient ;
  15717.                      GpiLine (hps, &ptl) ;
  15718.  
  15719.                      GpiMove (hps, aptlLineStart + TXTBOX_TOPLEFT) ;
  15720.                      ptl.x = 0 ;
  15721.                      GpiLine (hps, &ptl) ;
  15722.                      }
  15723.                 GpiCloseSegment (hps) ;
  15724.                 break ;
  15725.  
  15726.            case WM_PAINT:
  15727.                 /* WinBeginPaint (hwnd, hps, NULL) ; */
  15728.  
  15729.                 GpiDrawChain (hps) ;
  15730.  
  15731.                 /* WinEndPaint (hps) ; */
  15732.  
  15733.                 WinValidateRect (hwnd, NULL, FALSE) ;   /* temp fix */
  15734.                 break ;
  15735.  
  15736.            case WM_DESTROY:
  15737.                 GpiDeleteSegment (hps, lSegmentName) ;
  15738.                 GpiAssociate (hps, NULL) ;
  15739.                 GpiDestroyPS (hps) ;
  15740.                 break ;
  15741.  
  15742.            default:
  15743.                 return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  15744.            }
  15745.       return FALSE ;
  15746.       }
  15747.  
  15748.  ████████████████████████████████████████████████████████████████████████████
  15749.  
  15750.  Using OS/2 Semaphores to Coordinate Concurrent Threads of Execution
  15751.  
  15752.  Kevin Ruddell
  15753.  
  15754.  OS/2 semaphores are a powerful and general mechanism for coordinating
  15755.  multiple concurrent threads of execution. One common use of semaphores
  15756.  is to serialize access to pieces of code and the resources referenced by
  15757.  the code. The other common use of semaphores is to allow one thread to
  15758.  signal another thread that an event has occurred, for example, that an I/O
  15759.  operation has been completed. The OS/2 systems fully support both these
  15760.  uses of semaphores.
  15761.  
  15762.  There are four types of semaphores in OS/2: exclusive system,
  15763.  nonexclusive system, RAM, and FSRam (Fast-Safe Ram). FSRam semaphores are
  15764.  a new addition to Version 1.1 of OS/2. Nonexclusive system and RAM
  15765.  semaphores can be used either for serialization or signaling, while
  15766.  exclusive system and FSRam semaphores are intended only for
  15767.  serialization. FSRam semaphores are intermediate in safety and their
  15768.  performance is comparable to that of RAM semaphores.
  15769.  
  15770.  System semaphores can be used by threads of different processes that do
  15771.  not share memory and they offer some safety advantages over the other two
  15772.  types. Future versions of OS/2 will provide additional protection
  15773.  mechanisms and network support for system semaphores.
  15774.  
  15775.  When access to a reusable resource, such as a file, a data structure, or
  15776.  the screen must be serialized, we associate a semaphore with the resource
  15777.  and require that the semaphore have only one owner at a time. A thread
  15778.  then attempts to gain ownership of a semaphore by issuing a DosSemRequest
  15779.  or DosFSRamSemRequest call. If the semaphore is unowned, the call returns
  15780.  a value of zero and the calling thread now owns the semaphore. When the
  15781.  thread is already owned, the call either returns immediately (if the
  15782.  Timeout parameter is zero) or else waits n milliseconds (if Timeout = n)
  15783.  or it waits indefinitely (if Timeout = -1). We say the thread is blocked
  15784.  if it must wait for the completion of a semaphore request. If the
  15785.  requesting thread gains ownership of the semaphore before the timeout
  15786.  period expires, it immediately returns a zero value. Otherwise, it waits
  15787.  until the timeout period has elapsed and returns a non-zero value. A
  15788.  thread surrenders ownership of a semaphore by issuing a DosSemClear or
  15789.  DosFSRamSemClear call.
  15790.  
  15791.  FSRam and exclusive system semaphores (system semaphores where the
  15792.  NoExclusive option was not selected in the DosCreateSem request that
  15793.  created the semaphore) are well suited to serialization. RAM semaphores
  15794.  and nonexclusive system semaphores can provide serialization, but you
  15795.  should follow some guidelines when using them.
  15796.  
  15797.  Whether semaphores are used for serialization or for signaling, they can
  15798.  cause the hazards of deadlock, violations of serialization, and process
  15799.  termination while owning resources. By careful analysis of the
  15800.  application, knowledge of the OS/2 semaphore functions, and employment
  15801.  of some simple rules, these hazards can be avoided.
  15802.  
  15803.  ───────────────────────────────────────────────────────────────────────────
  15804.  Basic semaphore operation for serialization of reusable resources.
  15805.  ───────────────────────────────────────────────────────────────────────────
  15806.  
  15807.                                    ──────────────────
  15808.               ─┐ ╔═══════════╗       DosSemRequest
  15809.  sem={1,2,...n}├─╢THREAD{sem}╟─── DosFSRamSemRequest
  15810.                │ ║issues call║     ────────┬─────────
  15811.       THREAD{1}│ ╚═══════════╝             
  15812.       THREAD{2}│                           R  ───────────────────┐
  15813.          ∙     │                                                 │
  15814.          ∙     │                 THREAD{sem} owns  R              │
  15815.          ∙     │         ───────────────────────────────────      │
  15816.       THREAD{n}│         When THREAD{sem} owns the resource       │
  15817.              ─┘         all other threads requesting access      │
  15818.          │                      to  R  are blocked                │
  15819.                          ──────────────────┬────────────────      │
  15820.          │                        ╔═════════════════╗            │
  15821.                                   ║THREAD{sem} issues║            │
  15822.          │                        ║    DosSemClear   ║            │
  15823.      ╔═════════════════════╗      ║ DosFSRamSemClear ║            │
  15824.      ║ THREAD{sem} either  ║      ╚══════════════════╝            
  15825.      ║ reissues a request  ║    ────────────────────────    ╔════════════╗
  15826.  ───║ for  R  or goes off ║───THREAD{sem} relinquishes───║     R      ║
  15827.      ║to do something else.║        ownership of  R         ║  becomes   ║
  15828.      ╚═════════════════════╝    ────────────────────────    ║ availiable ║
  15829.      R  = Reusable Resource and an associated semaphore     ╚════════════╝
  15830.  
  15831.  
  15832.  Semaphore Types
  15833.  
  15834.  When you write a program with semaphores, you should ask yourself two
  15835.  questions: Is this semaphore for serialization or signaling? Will it be
  15836.  used between threads of one process or between different processes? The
  15837.  answers to these questions indicate which type of semaphore you should
  15838.  use and the system calls with which you will access it. Generally, you
  15839.  should use RAM semaphores for serialization of or synchronization
  15840.  between threads within one process, FSRam semaphores or exclusive system
  15841.  semaphores for serialization of threads in different processes, and
  15842.  nonexclusive system or RAM semaphores for signaling between threads in
  15843.  different processes.
  15844.  
  15845.  All OS/2 semaphore operations are performed on a semaphore handle,
  15846.  which is a 32-bit quantity used to access the semaphore. In the case of
  15847.  a RAM or FSRam semaphore, the handle is the semaphore data structure's
  15848.  address; in the case of a system semaphore, it is the value that is
  15849.  returned by the DosCreateSem or by the DosOpenSem call.
  15850.  
  15851.  
  15852.  RAM Semaphores
  15853.  
  15854.  RAM semaphores are mainly useful for coordinating the execution of
  15855.  threads within a process. They are fast, since they use a simple data
  15856.  structure, a double word of data storage, and receive relatively little
  15857.  management or protection from OS/2.
  15858.  
  15859.  RAM semaphores can be used between processes that share memory, which
  15860.  could be created by a DosAllocShrSeg call. However, RAM semaphores should
  15861.  not be used for serialization between processes, since OS/2 will not free
  15862.  a RAM semaphore if the owner terminates while owning it. They are fine,
  15863.  though, for providing serialization between threads within one process
  15864.  and can be employed for signaling within or between processes.
  15865.  
  15866.  The RAM semaphore data structure is a double word of storage that must be
  15867.  initialized to 0 (unowned/clear) before it is used as a semaphore. It
  15868.  should then be accessed using any of the following: DosSemClear,
  15869.  DosSemRequest, DosSemSet, DosSemSetWait, DosSemWait, or DosMuxSemWait.
  15870.  
  15871.  
  15872.  System Semaphores
  15873.  
  15874.  System semaphores are the most flexible, safe, and easy-to-use type of
  15875.  semaphore, but the overhead needed to maintain them exacts a performance
  15876.  penalty. They do not require shared memory, and their ownership is
  15877.  relinquished when their owner terminates, so they are suitable for
  15878.  serialization between processes.
  15879.  
  15880.  Each process that wishes to use a system semaphore must get a handle from
  15881.  OS/2 by making a DosCreateSem or DosOpenSem call. The process supplies a
  15882.  null-terminated semaphore name with the same format as the name of a file
  15883.  in the subdirectory \SEM\, for example, \SEM\RESOURCE.LCK. OS/2 then
  15884.  returns a handle for future access. The process also specifies whether
  15885.  nonowning processes can alter the state of the semaphore while it is
  15886.  owned. System semaphores are usually created nonexclusive for signaling
  15887.  applications and exclusive for serialization applications.
  15888.  
  15889.  DosCreateSem is used if the semaphore does not already exist (an error
  15890.  code is returned if it exists) and initializes the new semaphore to
  15891.  unowned. DosOpenSem is used if another process has created the system
  15892.  semaphore, and an error code is returned if it does not exist. If, in your
  15893.  application, one process will definitely run first, it can issue
  15894.  DosCreateSem; later processes can then issue DosOpenSem. If the order of
  15895.  execution of processes is not known, each one can call DosCreateSem and,
  15896.  if this fails because the semaphore already exists, then call DosOpenSem.
  15897.  
  15898.  When a process no longer needs to reference a system semaphore, it should
  15899.  issue a DosCloseSem call. The system semaphore itself will be deleted when
  15900.  all of the processes using the semaphore have called DosCloseSem. If a
  15901.  process terminates with open system semaphores, then the semaphores are
  15902.  closed by the system (see Figure 1).
  15903.  
  15904.  Normally, a thread should not request a semaphore while it already owns
  15905.  it. This would cause it to block waiting for itself to free the semaphore,
  15906.  resulting in deadlock.
  15907.  
  15908.  A system semaphore created with the exclusive option does not cause a
  15909.  thread to block if the thread makes a DosSemRequest while owning the
  15910.  semaphore. Instead, the semaphore's use count is incremented. Each time
  15911.  the owning thread issues a DosSemClear, the semaphore's use count is
  15912.  decremented, and when the use count becomes 0, the semaphore is released.
  15913.  Note that such a semaphore is not the same as a classical counting
  15914.  semaphore, which will be simulated later in this article. If another
  15915.  thread attempts to do a DosSemRequest on an exclusive semaphore that is
  15916.  already owned, it will block and wait for the use count to go to 0.
  15917.  
  15918.  Nonexclusive system semaphores behave just as RAM semaphores do when they
  15919.  are used with DosSemClear, DosSemRequest, DosSemSet, DosSemSetWait,
  15920.  DosSemWait, and DosMuxSemWait.
  15921.  
  15922.  
  15923.  FSRam Semaphores
  15924.  
  15925.  FSRam semaphores provide serialization between processes, combining most
  15926.  of the speed of RAM semaphores with much of the safety of system
  15927.  semaphores. They use a simple data structure and have a small system
  15928.  overhead, so their performance is very good. Since they store the data to
  15929.  track semaphore ownership properly, FSRam semaphores permit DosExitList
  15930.  processing to free an owned resource when the owning process terminates.
  15931.  
  15932.  Just as with exclusive system semaphores, FSRam semaphores support
  15933.  recursive DosFSRamSemRequest's by incrementing a use count, and support
  15934.  DosFSRamSemClear's by decrementing a use count. When the use count goes
  15935.  to 0, the semaphore becomes unowned. (Again, note that such a semaphore is
  15936.  different from a classical counting semaphore.) DosFSRamSemRequest and
  15937.  DosFSRamSemClear are the only OS/2 calls available for FSRam semaphores.
  15938.  
  15939.  The FSRam semaphore data structure exists in memory shared by the
  15940.  cooperating processes and must have its length field set to 12 and all of
  15941.  its other fields initialized to 0 before its first use.
  15942.  
  15943.  ───────────────────────────────────────────────────────────────────────────
  15944.  Comparison of Semaphore Types
  15945.  ───────────────────────────────────────────────────────────────────────────
  15946.  
  15947.    ┌────────────────────────────────────────────────────────────────────┐
  15948.    │Semaphore │                               │            │            │█
  15949.    │Types     │            Use                │Protection  │Performance │█
  15950.    ├──────────┼─────────────┬─────────────────┼────────────┼────────────┤█
  15951.    │          │             │Between Threads  │            │            │█
  15952.    │Exclusive │Serialization│in Different     │Some Safety │            │█
  15953.    │System    │Only         │Processes        │Provided    │            │█
  15954.    ├──────────┼─────────────┼─────────────────┤Managed     │Moderate    │█
  15955.    │Non-      │Serialization│Between Threads  │by OS/2     │            │█
  15956.    │exclusive ├─────────────┤in Different     │            │            │█
  15957.    │System    │Signaling    │Processes        │            │            │█
  15958.    ├──────────┼─────────────┼─────────────────┼────────────┼────────────┤█
  15959.    │          │             │Between Threads  │            │            │█
  15960.    │          │Serialization│Within One       │Minimal     │            │█
  15961.    │          │             │Process          │Protection──│            │█
  15962.    │RAM       ├─────────────┼─────────────────┤Not         │High        │█
  15963.    │          │             │Between Threads  │Managed     │            │█
  15964.    │          │Signaling    │in the Same or   │by OS/2     │            │█
  15965.    │          │             │Different Process│            │            │█
  15966.    ├──────────┼─────────────┼─────────────────┼────────────┼────────────┤█
  15967.    │          │             │                 │Some        │            │█
  15968.    │FSRam     │Serialization│Between Threads  │Protection──│            │█
  15969.    │(Fast-Safe│Only         │in Different     │Partially   │High        │█
  15970.    │Ram)      │             │Processes        │Managed     │            │█
  15971.    │          │             │                 │by OS/2     │            │█
  15972.    └──────────┴─────────────┴─────────────────┴────────────┴────────────┘█
  15973.      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  15974.  
  15975.  
  15976.  Serialization
  15977.  
  15978.  In OS/2, semaphores can ensure that no more than one thread at a time has
  15979.  access to a protected resource. This is done by assigning a semaphore to
  15980.  the protected resource and adopting the programming convention that each
  15981.  piece of code that accesses the protected resource must be preceded by a
  15982.  call to DosSemRequest (or one to DosFSRamSemRequest) and must be followed
  15983.  by a call to DosSemClear (or one to DosFSRamSemClear). It is essential
  15984.  that every thread accessing the protected resource follow this procedure,
  15985.  or time-dependent errors may occur.
  15986.  
  15987.  The thread whose semaphore request returns without error or time-out
  15988.  "owns" the semaphore, and all other threads that issue a similar request
  15989.  will block. This permits the owning thread to have exclusive access to the
  15990.  code that affects the protected resource. When the owning thread no longer
  15991.  needs to access the protected resource, it issues a semaphore clear and
  15992.  thereby gives up its ownership. If another thread is blocked because of a
  15993.  request on the newly released semaphore, it is permitted to proceed and
  15994.  access the protected resource.
  15995.  
  15996.  For example, suppose two threads use a semaphore to serialize access to a
  15997.  resource. You might end up with code such as that shown in Figure 2, where
  15998.  the ──1 indicates that the calling thread will wait indefinitely until the
  15999.  requested semaphore becomes available.
  16000.  
  16001.  The programming convention described previously should always be strictly
  16002.  followed unless it is unnecessary for a particular application. Even a
  16003.  slight deviation can lead to a failure of serialization, deadlock, or
  16004.  both.
  16005.  
  16006.  An FSRam semaphore can be safely used for serialization between threads of
  16007.  different processes if each process provides for recovery in case it
  16008.  terminates while owning the semaphore. Before terminating, each process
  16009.  should set up an ExitList routine to ensure the integrity of the resource
  16010.  protected by the semaphore by making a call to DosExitList. When the
  16011.  process terminates, the ExitList routine will execute and should first
  16012.  issue DosFSRamSemRequest to acquire the semaphore, then clean up the
  16013.  resource, and finally issue DosFSRamSemClear in order to free the
  16014.  resource for use by other processes.
  16015.  
  16016.  If the FSRam semaphore is owned by a thread of the terminating process,
  16017.  at the point when DosFSRamSemRequest is issued during the execution of an
  16018.  ExitList routine, the owning thread ID is forced to become the current
  16019.  thread ID and the use count for the semaphore is set to 1. This allows the
  16020.  ExitList routine to put the resource into a consistent state and then
  16021.  free it with DosFSRamSemClear (see Figure 3).
  16022.  
  16023.  RAM semaphores should only be used to serialize threads within a single
  16024.  process. In this case, they are efficient (since there is little system
  16025.  overhead), convenient (since the threads inherently share memory), and
  16026.  safe (since the process only ends when all of its threads end).
  16027.  
  16028.  It is possible to use a nonexclusive system semaphore for serialization,
  16029.  but all threads that access the semaphore must follow the programming
  16030.  conventions for serialization. Nonexclusive system semaphores
  16031.  generally behave in the same way RAM semaphores do, except that if the
  16032.  owner dies while owning one, the next thread to return from a
  16033.  DosSemRequest on it will own it and get an error code. The new owner, an
  16034.  ordinary thread or an ExitList routine, can clean up the resource and
  16035.  issue DosSemClear.
  16036.  
  16037.  Exclusive system semaphores can be safely used to provide serialization
  16038.  between different processes, since the system semaphore data structure
  16039.  contains the ID of the owning thread, and any attempt by one thread to
  16040.  change the state of an exclusive system semaphore owned by another thread
  16041.  will cause the thread to block or report an error.
  16042.  
  16043.  When a thread ends, either individually or because its process
  16044.  terminates, while owning a system semaphore, a flag is set in the
  16045.  semaphore data structure. The next thread that gets the semaphore by means
  16046.  of a call to DosSemRequest gets the error code "owner ended owning
  16047.  semaphore" and can take suitable recovery measures. A subsequent call to
  16048.  DosSemClear will free the semaphore and clear the error flag.
  16049.  
  16050.  ExitList processing for system semaphores used for serialization is very
  16051.  similar to the ExitList processing for FSRam semaphores.
  16052.  
  16053.  
  16054.  Signaling
  16055.  
  16056.  RAM or nonexclusive system semaphores allow one thread to detect an event
  16057.  in another . For example, suppose code fragment F1 in thread1 must
  16058.  execute before F2 in thread2, and the system semaphore with handle sigSem
  16059.  has been created with the NoExclusive option and has been set with
  16060.  DosSemSet. The two threads could appear as shown in Figure 4.
  16061.  
  16062.  In the worst case, thread2 will execute the call to DosSemWait before
  16063.  thread1 finishes executing F1, at which time it blocks. When thread1
  16064.  finishes executing F1, it clears sigSem, and thread2 proceeds to execute
  16065.  F2.
  16066.  
  16067.  The semaphore functions DosSemSet, DosSemClear, DosSemWait,
  16068.  DosSemSetWait, and DosMuxSemWait are used for signaling. DosSemSet sets a
  16069.  semaphore, and DosSemClear clears it. DosSemWait blocks the current thread
  16070.  until the semaphore is cleared or until an optional time-out interval has
  16071.  elapsed. DosSemSetWait is an indivisible concatenation of DosSemSet
  16072.  followed by DosSemWait. DosMuxSemWait blocks the current thread until one
  16073.  of a list of semaphores is cleared or until an optional time-out interval
  16074.  is over.
  16075.  
  16076.  You should not use the signaling calls with exclusive system semaphores.
  16077.  As an example, consider a thread that issues DosSemSetWait. If the
  16078.  exclusive system semaphore is already owned, an error is returned.
  16079.  Otherwise, the thread sets the semaphore and waits for it to clear.
  16080.  However, this will never happen since no other thread can clear it.
  16081.  
  16082.  Figure 5 is an example of the use of DosSemSetWait, similar to the one
  16083.  mentioned above with DosSemWait but this time in an infinite loop. The
  16084.  code fragment F2 can execute only once for each execution of F1.
  16085.  
  16086.  Figure 6 demonstrates the use of DosMuxSemWait. In this case, thread4
  16087.  blocks until thread1, thread2, or thread3 clears its semaphore. Thread4
  16088.  proceeds and takes whatever action is appropriate in response to the
  16089.  signaling thread.
  16090.  
  16091.  As with serialization, a thorough analysis is necessary when using
  16092.  semaphores to implement signaling in order to avoid the hazards of deadlock
  16093.  and synchronization failure.
  16094.  
  16095.  
  16096.  Circular Buffers
  16097.  
  16098.  Suppose two threads of one process are communicating via a circular
  16099.  buffer, that is, thread1 writes to Buffer and thread2 reads from Buffer.
  16100.  Buffer has a fixed capacity, bufSize, and pointers to the locations of the
  16101.  newest (head) and oldest (tail) items. The buffer is circular in the sense
  16102.  that each thread accesses location 0 after it accesses location bufSize──1.
  16103.  Buffer is empty when head=tail and is full when tail=head+1 (mod bufSize).
  16104.  
  16105.  Suppose that mutexSem, emptySem, and fullSem are RAM semaphores and that
  16106.  
  16107.    DosSemClear(&mutexSem);
  16108.    DosSemSet(&emptySem);
  16109.    DosSemClear(&fullSem);
  16110.    head=tail=0;
  16111.  
  16112.  is executed before the threads are spun off. Then the two threads would
  16113.  appear as shown in Figure 7.
  16114.  
  16115.  The protected resource in this case is Buffer, together with its
  16116.  associated variables (head, tail, emptySem, and fullSem). Each thread
  16117.  accesses the protected resource between calls to DosSemRequest and to
  16118.  DosSemClear on the mutual exclusion semaphore, known as mutexSem, so the
  16119.  accesses are made serially, and there are no race conditions.
  16120.  
  16121.  Before the threads are spun off, the protected resource is in a consistent
  16122.  state: head==tail, emptySem is set, and fullSem is clear. Each thread
  16123.  changes the state of the protected resource so that if it was consistent
  16124.  before entering the critical section, it is consistent on leaving the
  16125.  critical section. For example, if thread1 adds an item that occupies the
  16126.  last available free space in Buffer, it then sets the fullSem semaphore.
  16127.  Thread1 always clears the emptySem semaphore since it has just added an
  16128.  item to Buffer. Similarly, thread2 sets the emptySem semaphore when it
  16129.  removes the last item from the buffer and always clears the fullSem
  16130.  semaphore.
  16131.  
  16132.  If Buffer is full, thread1 sets fullSem and then blocks on fullSem. In
  16133.  this case, emptySem is not set and, since thread1 is blocked outside its
  16134.  critical region, thread2 can enter its critical region, remove an item
  16135.  from the buffer, and clear fullSem, allowing thread1 to proceed.
  16136.  
  16137.  If Buffer is empty, thread2 sets emptySem and then blocks on emptySem.
  16138.  Since fullSem is not set and thread2 is blocked outside its critical
  16139.  section, thread1 can enter its critical section, add an item to Buffer,
  16140.  and clear emptySem, allowing thread2 to proceed.
  16141.  
  16142.  
  16143.  Simulating a Classical Counting Semaphore
  16144.  
  16145.  A classical counting semaphore can be easily simulated with the
  16146.  semaphore facilities that are offered by OS/2. ClassicCountSem is an
  16147.  integer variable that can only be accessed, apart from initialization,
  16148.  via the two atomic operations P and V (see Figure 8).
  16149.  
  16150.  To simulate classicCountSem and its associated operations P and V, use the
  16151.  OS/2 semaphores countSem for signaling and mutexSem for serialization,
  16152.  both initially clear, and the integer variable count, initially
  16153.  nonnegative (see Figure 9).
  16154.  
  16155.  All of the code in the P and V routines that changes count and countSem
  16156.  lies in mutual exclusion regions bracketed by calls on mutexSem,
  16157.  simplifying the analysis of these routines.
  16158.  
  16159.  These routines would work correctly with all references to countSem
  16160.  removed, but P would burn up a lot of cycles testing conditions that are
  16161.  not satisfied. The wait on countSem prevents useless activity, but it also
  16162.  allows P to proceed if there is a chance that it will run to completion. P
  16163.  will block on countSem only if count is 0. When V executes, count is
  16164.  incremented and countSem is cleared, permitting P to run until count is
  16165.  found to be 0 again. Therefore, whenever V "produces" a unit, any waiting
  16166.  P's have an opportunity to "consume" it.
  16167.  
  16168.  
  16169.  Scheduling
  16170.  
  16171.  The precise timing and behavior of threads synchronized by means of
  16172.  semaphores is affected by the activity of other threads in the system,
  16173.  such as priority and position in the list of blocked threads. If several
  16174.  threads are waiting on a semaphore, your application should not count on
  16175.  one in particular to be scheduled to run, as factors outside your control
  16176.  may affect the decision of the scheduler.
  16177.  
  16178.  DosSemWait, DosSemSetWait, and DosSemRequest are level-triggered. This
  16179.  means, for example, that a thread blocked by one of these calls will only
  16180.  return if the blocking semaphore remains clear until the blocked thread
  16181.  can actually run. If some other thread, in the meantime, sets the
  16182.  semaphore again, then the blocked thread will remain blocked. If the
  16183.  semaphore remains clear until the blocked thread can be scheduled and
  16184.  actually run, then the call returns and the once-blocked thread continues
  16185.  with its execution.
  16186.  
  16187.  DosMuxSemWait, on the other hand, is edge-triggered. That is, a thread
  16188.  that is blocked by a call to DosMuxSemWait will return if one of the
  16189.  semaphores in its list is cleared, even if the semaphore is set by another
  16190.  thread before the blocked thread can run again.
  16191.  
  16192.  
  16193.  Conclusion
  16194.  
  16195.  Semaphores in OS/2 can be used to provide serialized access to protected
  16196.  resources by associating a semaphore with the resource and combining OS/2
  16197.  function calls with programming conventions to ensure that the semaphore
  16198.  has only one owner at a time. Semaphores can also allow one thread to
  16199.  signal another that an event has occurred. By analyzing the particular
  16200.  application, you can pick the appropriate semaphore type and OS/2
  16201.  function calls.
  16202.  
  16203.  
  16204.  Figure 1:  Using System Semaphores
  16205.  
  16206.  if( rc=DosCreateSem( NoExclusive, &sysSem, &semName))
  16207.    if( rc == ERROR_ALREADY_EXISTS)
  16208.         DosOpenSem( &sysSem, &semName) ;
  16209.    else
  16210.            <...error...>
  16211.  
  16212.  <...use the semaphore...>
  16213.  
  16214.  DosCloseSem( sysSem) ;
  16215.  
  16216.  
  16217.  Figure 2:  Two Threads Serializing Access to a Resource
  16218.  
  16219.  long resourceSem = 0;
  16220.  
  16221.  thread1()
  16222.  {
  16223.                                      ∙
  16224.                                      ∙
  16225.                                      ∙
  16226.       DosSemRequest( &resourceSem, -1L);
  16227.  
  16228.       <... read/modify resource ...>
  16229.  
  16230.       DosSemClear( &resourceSem);
  16231.                                      ∙
  16232.                                      ∙
  16233.                                      ∙
  16234.  }
  16235.  
  16236.  thread2()
  16237.  {
  16238.                                      ∙
  16239.                                      ∙
  16240.                                      ∙
  16241.       DosSemRequest( &resourceSem, -1L);
  16242.  
  16243.       <... read/modify resource...>
  16244.  
  16245.       DosSemClear( &resourceSem);
  16246.                                      ∙
  16247.                                      ∙
  16248.                                      ∙
  16249.  }
  16250.  
  16251.  
  16252.  Figure 3:  ExitList Processing for a Fast-Safe Ram Semaphore
  16253.  
  16254.  main()
  16255.  {
  16256.                                      ∙
  16257.                                      ∙
  16258.                                      ∙
  16259.       DosExitList( 1, &Cleanup);    /* add to exit list */
  16260.                                      ∙
  16261.                                      ∙
  16262.                                      ∙
  16263.  }
  16264.  
  16265.  Cleanup()
  16266.  {
  16267.     if( DosFSRamSemRequest( &sem, 0L) != ERR_TIMEOUT)
  16268.     {
  16269.            <... put in a consistent state ...>
  16270.            DosFSRamSemClear( &sem);
  16271.     }
  16272.     DosExitList( 3, 0);  /* goto next ExitList routine */
  16273.  }
  16274.  
  16275.  
  16276.  Figure 4:  Signaling Between Threads
  16277.  
  16278.  DosSemSet( sigSem);
  16279.  
  16280.  thread1()
  16281.                                      ∙
  16282.                                      ∙
  16283.                                      ∙
  16284.       F1
  16285.       DosSemClear( sigSem);
  16286.                                      ∙
  16287.                                      ∙
  16288.                                      ∙
  16289.  }
  16290.  
  16291.  thread2()
  16292.                                      ∙
  16293.                                      ∙
  16294.                                      ∙
  16295.       DosSemWait( sigSem, -1L);
  16296.       F2
  16297.                                      ∙
  16298.                                      ∙
  16299.                                      ∙
  16300.  }
  16301.  
  16302.  
  16303.  Figure 5:  Using DosSemSetWait
  16304.  
  16305.  thread1()
  16306.                                      ∙
  16307.                                      ∙
  16308.                                      ∙
  16309.       while(1)
  16310.       {
  16311.            F1
  16312.            DosSemClear( sigSem) ;
  16313.       }
  16314.                                      ∙
  16315.                                      ∙
  16316.                                      ∙
  16317.  }
  16318.  
  16319.  thread2()
  16320.                                      ∙
  16321.                                      ∙
  16322.                                      ∙
  16323.       while(1)
  16324.       {
  16325.            DosSemSetWait( segSem, -1L) ;
  16326.            F2
  16327.       }
  16328.                                      ∙
  16329.                                      ∙
  16330.                                      ∙
  16331.  }
  16332.  
  16333.  
  16334.  Figure 6:  Using DosMuxSemWait
  16335.  
  16336.  struct {
  16337.     int numSem;
  16338.     int res1;
  16339.     unsigned long semHandle1;
  16340.     int res2;
  16341.     unsigned long semHandle2;
  16342.     int res3;
  16343.     unsigned long semHandle3;
  16344.  } muxSemList ;
  16345.  
  16346.  int muxIndex;
  16347.  
  16348.  thread1()
  16349.                                      ∙
  16350.                                      ∙
  16351.                                      ∙
  16352.       F1
  16353.       DosSemClear( sigSem1) ;
  16354.                                      ∙
  16355.                                      ∙
  16356.                                      ∙
  16357.  }
  16358.  
  16359.  thread2()
  16360.                                      ∙
  16361.                                      ∙
  16362.                                      ∙
  16363.  
  16364.       F2
  16365.       DosSemClear( sigSem2) ;
  16366.                                      ∙
  16367.                                      ∙
  16368.                                      ∙
  16369.  }
  16370.  
  16371.  thread3()
  16372.                                      ∙
  16373.                                      ∙
  16374.                                      ∙
  16375.       F3
  16376.       DosSemClear( sigSem3) ;
  16377.                                      ∙
  16378.                                      ∙
  16379.                                      ∙
  16380.  }
  16381.  
  16382.  thread4()
  16383.                                      ∙
  16384.                                      ∙
  16385.                                      ∙
  16386.  
  16387.       muxSemList.numSem = 3 ;
  16388.       muxSemList.res1 = 0;
  16389.       muxSemList.semHandle1 = sigSem1 ;
  16390.       muxSemList.res2 = 0 ;
  16391.       muxSemList.semHandle2 = sigSem2 ;
  16392.       muxSemList.res3 = 0 ;
  16393.       muxSemList.semHandle3 = sigSem3 ;
  16394.                                      ∙
  16395.                                      ∙
  16396.                                      ∙
  16397.       DosMuxSemWait( &muxIndex, &muxSemList, -1L) ;
  16398.       switch( muxIndex) {
  16399.       case 1:
  16400.                                      ∙
  16401.                                      ∙    /* respond to F1 */
  16402.                                      ∙
  16403.       case 2:
  16404.                                      ∙
  16405.                                      ∙    /* respond to F2 */
  16406.                                      ∙
  16407.       case 3:
  16408.                                      ∙
  16409.                                      ∙    /* respond to F3 */
  16410.                                      ∙
  16411.       }
  16412.  }
  16413.  
  16414.  
  16415.  Figure 7:  Example of a Circular Buffer
  16416.  
  16417.  thread1()
  16418.                                      ∙
  16419.                                      ∙
  16420.                                      ∙
  16421.       <... get item c ...>
  16422.       DosSemWait( &fullSem, -1L) ;
  16423.  
  16424.       DosSemRequest( &mutexSem, -1L);
  16425.       Buffer.head = c;              /* store c in Buffer */
  16426.       head++;                       /* advance head of Buffer */
  16427.       head %= bufSize;              /* wrap around to beginning */
  16428.       if((head==tail)||((tail==0)&&(head==bufSize-1)))
  16429.            DosSemSet( &fullSem);    /* set if full */
  16430.       DosSemClear( &emptySem);      /* not empty */
  16431.       DosSemClear( &mutexSem);
  16432.                                      ∙
  16433.                                      ∙
  16434.                                      ∙
  16435.  }
  16436.  
  16437.  thread2()
  16438.                                      ∙
  16439.                                      ∙
  16440.                                      ∙
  16441.       DosSemWait( &emptySem, -1L);
  16442.  
  16443.       DosSemRequest( &mutexSem, -1L);
  16444.       c = Buffer.tail;              /* get c from Buffer */
  16445.       tail++;                       /* advance tail of Buffer */
  16446.       tail %= bufSize;              /* wrap around to beginning */
  16447.       if( head==tail)
  16448.            DosSemSet( &emptySem);   /* set if empty */
  16449.       DosSemClear( &fullSem);       /* not full */
  16450.       DosSemClear( &mutexSem);
  16451.  
  16452.       <... use item c ...>
  16453.                                      ∙
  16454.                                      ∙
  16455.                                      ∙
  16456.  }
  16457.  
  16458.  
  16459.  Figure 8:  Classical Counting Semaphore
  16460.  
  16461.  P()
  16462.  {
  16463.       <... wait until classicCountSem > 0 ...>
  16464.       classicCountSem-;
  16465.  }
  16466.  
  16467.  V()
  16468.  {
  16469.       classicCountSem++;
  16470.  }
  16471.  
  16472.  
  16473.  Figure 9:  Simulating a Counting Semaphore under OS/2
  16474.  
  16475.   P()
  16476.   {
  16477.       int blocked=1;
  16478.  
  16479.       while( blocked == 1)
  16480.       {
  16481.            DosSemWait( &countSem);         /* wait til maybe ok */
  16482.  
  16483.            DosSemRequest( &mutexSem, -1L); /* mutual excl */
  16484.            if( count == 0)                 /* not ready yet */
  16485.              DosSemSet( &countSem);        /* set up block */
  16486.            else {
  16487.              count-;                       /* decrement count */
  16488.              blocked-;                     /* set up loop exit */
  16489.            }
  16490.            DosSemClear( &mutexSem);        /* mutual excl */
  16491.       }
  16492.   }
  16493.  
  16494.   V()
  16495.   {
  16496.       DosSemRequest( &mutexSem, -1L);       /* mutual excl */
  16497.       count++;                              /* increment count */
  16498.       DosSemClear( &countSem);              /* free waiters */
  16499.       DosSemClear( &mutexSem);              /* mutual excl */
  16500.   }
  16501.  
  16502.  ████████████████████████████████████████████████████████████████████████████
  16503.  
  16504.  Design Concepts and Considerations in Building an OS/2 Dynamic-Link Library
  16505.  
  16506.  Ross M. Greenburg
  16507.  
  16508.  You are in a maze of twisty little passages, all alike.
  16509.  
  16510.    >USE DYNAMIC-LINK LIBRARY
  16511.  
  16512.  I see no DLL here.
  16513.  
  16514.    >MAKE DLL
  16515.  
  16516.  I don't know how to make a DLL.
  16517.  
  16518.    >INVENTORY
  16519.  
  16520.  You have:
  16521.  
  16522.    ■  an 80286 machine
  16523.    ■  sufficient memory
  16524.    ■  OS/2
  16525.    ■  An OS/2 toolkit. In the toolkit is:
  16526.       ■  a text editor
  16527.       ■  a C compiler
  16528.       ■  an assembler
  16529.       ■  a linker
  16530.  
  16531.    >USE TOOLS IN TOOLKIT TO MAKE DLL
  16532.  
  16533.  Huh?
  16534.  
  16535.  Just as in the game ADVENTURE, not knowing the keywords when you're
  16536.  trying to create a dynamic-link library can be very frustrating. Once you
  16537.  know the keywords, though, you can explore new areas of the game and bring
  16538.  home prizes and treasures. The objective of this article is to help teach
  16539.  you some of the new keywords and techniques you need to build a dynamic-
  16540.  link library of your own.
  16541.  
  16542.  
  16543.  What Is a DLL?
  16544.  
  16545.  The idea of dynamic-link libraries is one of the most important
  16546.  concepts that the OS/2 systems introduce. Although the principle of DLLs
  16547.  has been around for some time-they are usually called shareable libraries
  16548.  in other operating systems-OS/2 makes such a shareable library an
  16549.  intrinsic part of the operating system. In fact, the system library
  16550.  functions themselves are DLLs in OS/2, and are clearly separated by
  16551.  device type, making upgrading easy.
  16552.  
  16553.  Under MS-DOS(R), after you compile a program, you then link it with other
  16554.  portions of the program and with portions from a library of commonly used
  16555.  routines. The end result is a standalone piece of code that is loaded
  16556.  into memory, has its outstanding address references resolved, and is then
  16557.  executed. The physical file resulting from the link contains portions of
  16558.  the library it used; two programs that use printf will each contain a copy
  16559.  of the library functions that comprise that ubiquitous function.
  16560.  
  16561.  In a single-tasking operating system, with a sufficiently large hard disk,
  16562.  this really isn't a problem. In a multitasking operating system that
  16563.  allows for shared memory use, loading multiple copies of the same code
  16564.  seems wasteful. OS/2 obviates this by permitting only one copy of a given
  16565.  function to be loaded in memory and to have this copy shared by any task
  16566.  that wants to use it. Since the function itself is not physically part of
  16567.  the program file, it is possible for the executable to be rather small
  16568.  and to update the library only as required. The concept of separate
  16569.  overlay files, and complicated linkers, is no longer needed; just include
  16570.  the specific DLLs needed and let the operating system do the rest.
  16571.  
  16572.  However, the advantages of using DLLs go far beyond the convenience; there
  16573.  is a functionality to DLLs that you can exploit in many different ways.
  16574.  For example, there is the ability for two or more completely separate
  16575.  and distinct programs to share memory by simply accessing the same run-
  16576.  time routine. This communication, already an intrinsic part of OS/2, can
  16577.  be fine-tuned with DLLs to fit your exact needs in a complicated
  16578.  environment.
  16579.  
  16580.  This is not without a price, though; there are some tricks, cautions, and
  16581.  caveats to writing an operable DLL. I discovered some of them the hard way
  16582.  while preparing a simple example of DLL use. Of course, if you want to
  16583.  avoid the supposed complexity of these useful techniques, there is
  16584.  nothing in OS/2 to prevent you from using the standard, and more
  16585.  familiar, linking techniques of the past, except, perhaps, the knowledge
  16586.  that there is a better way.
  16587.  
  16588.  A dynamic-link library is not simply a new method of linking an old
  16589.  library. There are some intrinsic differences between the two techniques.
  16590.  A look at how statically linked libraries are linked into your code will
  16591.  help you to understand how the new Dynalink approach differs and why it is
  16592.  an improvement.
  16593.  
  16594.  
  16595.  Static Linking
  16596.  
  16597.  When you compile a standard C program, the resulting output of the
  16598.  compiler is an object file, which contains a number of different types of
  16599.  records for procedures and routines, externally accessible variables,
  16600.  local stack variables, and so on. Each item has a unique record type
  16601.  associated with it.
  16602.  
  16603.  There is also a unique record type that indicates where the code for a
  16604.  routine starts. Another record type has the name of the routine and a
  16605.  pointer to that routine's code. Some record types indicate that the
  16606.  routine requested is not found and hence must be external to the object
  16607.  module; other record types indicate that the object is an externally
  16608.  located data item.
  16609.  
  16610.  Each call to a routine that cannot be resolved within the particular
  16611.  source module is changed into a parameter that simply involves an
  16612.  "external item" record.You can help the compiler by specifying the call
  16613.  type as being a near or far routine, or a near or far external item of
  16614.  some other type.
  16615.  
  16616.  After you've finished compiling all your various source modules into
  16617.  their object modules, you link them together, along with some
  16618.  appropriate libraries, and end up with an executable piece of code. The
  16619.  linker examines each object module it sees, usually in the order in which
  16620.  they're presented, and keeps a list of all record types that either
  16621.  indicate a request for an external item or define a global item. Then, as
  16622.  it sees record types indicating actual code routines, it determines that
  16623.  routine's placement and resolves all calls for it into an actual address
  16624.  within the eventual output file. Far calls, of course, indicate not
  16625.  just an offset within a 64Kb segment, but allow additional segments to be
  16626.  addressed, which in turn permit the creation of much larger programs.
  16627.  
  16628.  There is really nothing intrinsically foreign to the compiler in the
  16629.  concept of mixed memory-model code as long as it knows how a routine will
  16630.  be called. The compiler generates a far return for routines defined as far
  16631.  calls and near returns for near calls. Addressing far data items is
  16632.  resolved in a similar fashion: the compiler puts out a record type that
  16633.  the linker can understand and resolve into an actual segment and offset.
  16634.  There is an extra step for the actual loading and executing of the code,
  16635.  discussed later in this article.
  16636.  
  16637.  Whatever items are not resolved within the linking of the various object
  16638.  modules are next searched for in the libraries. These libraries are
  16639.  basically object modules with nothing but local references resolved. A
  16640.  module in the library usually starts off as a simple object file and is
  16641.  then stored and indexed into a library as an entire unit. It is not stored
  16642.  on a routine-by-routine basis, but on an object-file-by-object-file basis
  16643.  instead.
  16644.  
  16645.  The appropriate routine or external item is found in the library, and the
  16646.  module is then pulled from the library and inserted into the executable
  16647.  form. All references to it are resolved, and the process continues. An
  16648.  important consideration is that the object module originally loaded
  16649.  into the library as one unit is pulled from it as one unit as well, even
  16650.  if only one of the functions specified in the routine is referenced.
  16651.  
  16652.  The end result is a totally self-contained image, which, when executed, is
  16653.  loaded at some address, called the base address. The base segment address
  16654.  is added to all of the other segment addresses throughout the code in the
  16655.  mysterious load routines, and finally, with one simple call or jump, your
  16656.  program is executed. That's a basic, not-too-technical description of how
  16657.  static linking works.
  16658.  
  16659.  The differences between dynamic linking and the idea of statically linking
  16660.  an already existing library are not all that substantial, but the end
  16661.  product is. Consequently, the conceptual design of the dynamic-link library
  16662.  is different.
  16663.  
  16664.  
  16665.  Dynamic Linking
  16666.  
  16667.  With a normal library, you compile all of the object modules you need and
  16668.  then use a librarian program to create a library. The library itself is in a
  16669.  strange format, suitable only for linkers and librarian programs.
  16670.  
  16671.  Things are a little different with DLLs, though. First, there are two
  16672.  separate link steps. You must link the constituent object file members
  16673.  that form the DLL together and then link your own code with the resulting
  16674.  DLL. Creating the DLL itself, however, requires some work.
  16675.  
  16676.  After you've created the object files that will make up DLL, you link them
  16677.  and a special module definition file with the normal linker to create
  16678.  DLL. Its format is really no different from a normal EXE file-it even
  16679.  has the "MARKZIBO" as its first two bytes. It is therefore well suited for
  16680.  the standard system loader to load as if it were actually a program.
  16681.  Later on, the system does just that for the initialization routine. The
  16682.  new library will have an extension of DLL, as this name is hardwired into
  16683.  any affiliated EXE files.
  16684.  
  16685.  The special module definition file, or DEF, describes the external
  16686.  interface for each of the accessible routines: their public names and
  16687.  their attributes. Anything not specifically mentioned in the DEF file
  16688.  cannot be routinely accessed by an outside program. This definition file
  16689.  is called the export module definition file.
  16690.  
  16691.  By running the export DEF file through a program called Import Librarian
  16692.  (IMPLIB), you can create a special library file. This library file is
  16693.  conceptually similar to the standard notion of a library and hence has
  16694.  the LIB extension (see Figure 1).
  16695.  
  16696.  Instead of providing a LIB file, another option is to create what is in
  16697.  essence the inverse of the export DEF file. Such a file is called an import
  16698.  module definition file, which also has the extension DEF (see Figure 2).
  16699.  
  16700.  When you link the DLL with your own code, the linker sees the special
  16701.  record format of the import library (the LIB file created by IMPLIB), or
  16702.  reads the import DEF file and creates special records that OS/2's program
  16703.  load facilities understand. The end result of a link using DLLs is a
  16704.  hybrid file that can be considered a partial EXE and a partial OBJ at the
  16705.  same time. A compiled object module resolves local variables and routines
  16706.  into a segment and an offset, leaving external references virtually
  16707.  undefined. The Dynalink program results in an EXE coming from the linker
  16708.  with its external references to DLL routines effectively unresolved.
  16709.  
  16710.  At this point, I'm going to refer to segments as selectors, since I am
  16711.  referring to DLLs implemented under OS/2 running in protected mode.
  16712.  
  16713.  Part of OS/2's program loader recognizes that the EXE it's about to load
  16714.  contains DLL calls. Finding these records causes a lookup on an internal
  16715.  table to determine if the DLL has already been loaded. Only one copy of
  16716.  any code within the DLL is loaded-all sessions and processes share this
  16717.  copy.
  16718.  
  16719.  Each module in the DLL is defined as a "load-at-run-time" or a "load-on-
  16720.  demand" module. Regardless of which definition is chosen, a selector is
  16721.  allocated for each module, and all references to those modules are now
  16722.  resolved into a selector and offset pair. If the module has been defined
  16723.  as a load-at-run-time module, then the actual code for the module is read
  16724.  from the file and loaded into memory, and any outstanding linkages are
  16725.  resolved. When trying to locate a DLL, by the way, the operating system
  16726.  looks in the directory paths specified in your environment's LIBPATH
  16727.  variable.
  16728.  
  16729.  
  16730.  Value of Protected Mode
  16731.  
  16732.  Consider what happens when a load-on-demand function is called before that
  16733.  selector points to valid code: a page fault occurs, and the memory
  16734.  management module can easily resolve what the problem is, load the
  16735.  appropriate code, and let the program continue operating as if nothing
  16736.  had happened. Subsequent calls to routines within the same selector would
  16737.  operate without a page fault. Once the page fault mechanism, an intrinsic
  16738.  part of OS/2 and protected mode applications, has been enabled, it is
  16739.  transparent whether or not a requested page exists in real memory or in
  16740.  virtual memory.
  16741.  
  16742.  The 80286 and 80386 chips have a table within them called the Local
  16743.  Descriptor Table, or LDT, which holds selectors and the characteristics of
  16744.  these selectors. There is an LDT for each of the processes currently
  16745.  running. If a process attempts to access memory by using a selector not
  16746.  within its LDT, the hardware will cause a fault to occur──effective
  16747.  hardware protection of memory space.
  16748.  
  16749.  The Global Descriptor Table, or GDT, is similar to the LDT (see Figure 3),
  16750.  except that all tasks can access the selectors, and their associated
  16751.  memory, contained therein. Although this seems like a simple way to make a
  16752.  selector and its data space accessible to multiple processes, OS/2 does
  16753.  not use the GDT for shared memory access; instead it makes an entry into
  16754.  the LDT of each process. This is because memory space used by a DLL should
  16755.  only be accessible to the processes actually using the DLL. Using the GDT
  16756.  would make the DLL selectors available to every session and process on
  16757.  the system.
  16758.  
  16759.  When a request for memory allocation is made to OS/2, the type of memory
  16760.  (shared or nonshared) is included in the request, and an entry is made in
  16761.  the LDT for all processes permitted to share this memory. Actually,
  16762.  whenever shareable memory is allocated, an entry is made in every
  16763.  process's LDT.
  16764.  
  16765.  Processes requiring access to the memory have proper entries in the LDT;
  16766.  other tasks have illegal contents that will cause a memory fault to be
  16767.  generated if they are used to access DLL-allocated memory. Only the
  16768.  kernel (Ring 0) code can write to these tables, however. (Device drivers
  16769.  also run at Ring 0, so they'd have write access to the descriptor tables
  16770.  as well.)
  16771.  
  16772.  
  16773.  What's the Big Deal?
  16774.  
  16775.  So far, a relatively efficient mechanism exists for linking in routines as
  16776.  required at run time instead of just once at link, all automatically and
  16777.  transparently. However, just what are the advantages of such an
  16778.  ability? First, swapping the DLL routines in and out of memory becomes
  16779.  pretty easy; the LDT has a "present" bit indicating whether the
  16780.  requested segment is in memory or not. If not in memory, a page fault
  16781.  occurs as described above, and the swapped-out DLL routine can be brought
  16782.  into real memory. Since the selector itself is just an index into a table
  16783.  that contains real address information, the individual DLL modules can
  16784.  end up anywhere in memory, transparent to your own code.
  16785.  
  16786.  Program code without some data space associated with it is a rarity; pure
  16787.  code can't manipulate items, although it is frequently useful in
  16788.  mathematical routines. The 8086/8088 family of processors used the data
  16789.  segment register to address its data space. The 80286/80386 family of
  16790.  chips requires data to be addressed through a selector as well. The
  16791.  information for the data selector is also stored in the LDT. By setting
  16792.  the appropriate bits in the LDT entry for a given selector, its associated
  16793.  memory can be made private or publicly accessible, or it can be set to be
  16794.  written to as read-only. Data selectors can even require a certain level
  16795.  of privilege in the code attempting to access it. Any illegal operation
  16796.  causes a fault to occur, and OS/2 can deal with the faulting process as
  16797.  required.
  16798.  
  16799.  This means that, with the LDT set properly, memory can be shareable
  16800.  between tasks and protected from illegal or erroneous access, and other
  16801.  interesting memory uses and control techniques are made possible.
  16802.  
  16803.  As such, the DLL can be controlled and fine-tuned in a variety of
  16804.  different ways, through the DEF files.
  16805.  
  16806.  
  16807.  Defining the DEF File
  16808.  
  16809.  There are two different types of DEF files. The EXPORT definition file
  16810.  lets the world know what the various entry points and their characteristics
  16811.  are. The IMPORT definition file indicates which functions of many potential
  16812.  DLLs will be used and should therefore be linked at run time. A DLL can have
  16813.  both IMPORTS and EXPORTS defined in the same DEF file.
  16814.  
  16815.  The IMPORT library is created by processing the EXPORT definition file
  16816.  through IMPLIB. Let's look at each piece separately, along with its
  16817.  available features and options. These options must all be entered in the
  16818.  appropriate DEF file in uppercase.
  16819.  
  16820.  
  16821.  LIBRARY Statement
  16822.  
  16823.  The EXPORT DEF file requires several fields (see Figure 4). The most
  16824.  important one that is required is the LIBRARY field, which defines the
  16825.  file as a DLL Export definition file, instead of a normal application DEF
  16826.  file.
  16827.  
  16828.  The LIBRARY statement must be the first one in the DEF file, allowing the
  16829.  linker (and IMPLIB) to have a head start on what is to come. The first
  16830.  argument, [name], to the LIBRARY statement is the eventual output name
  16831.  for the created DLL. The filetype, or extension, will automatically be
  16832.  DLL.
  16833.  
  16834.  When the DLL is first loaded, you may want to initialize some things, for
  16835.  instance, setting specific data items, and assuring that certain system
  16836.  resources are available. Each DLL can have an initialization routine that
  16837.  will be called when the DLL is first loaded and there are no other
  16838.  references to it, or upon each invocation of the DLL. The second argument,
  16839.  [init_type], lets you specify whether you want the initialization routine
  16840.  called every time the DLL is invoked (INITINSTANCE), or once, when the DLL
  16841.  is first loaded (INITGLOBAL, the default).
  16842.  
  16843.  
  16844.  NAME Statement
  16845.  
  16846.  Similarly, if the linker sees the NAME statement, it understands that you
  16847.  are creating an application and not a DLL. The NAME statement allows you
  16848.  to specify whether the application is Windows- or Presentation Manager-
  16849.  compatible and, if so, whether it is capable of running in real mode or
  16850.  protected mode. If you specify WINDOWAPI as the second argument, then the
  16851.  application requires Windows in order to execute. Specifying WINDOWCOMPAT
  16852.  means that it is Windows-compatible and can run in a window group under
  16853.  OS/2. Finally, specifying NOTWINDOWCOMPAT indicates that the application
  16854.  requires its own screen group when running.
  16855.  
  16856.  The name command allows you to specify with [appname] the name the
  16857.  application will have after linking. Naturally, the default extension is
  16858.  EXE. You'll get a warning message from the linker if the name you choose
  16859.  for the executable name and the name you choose in the NAME line don't
  16860.  match.
  16861.  
  16862.  
  16863.  CODE Statement
  16864.  
  16865.  All code segments within the DLL share a similar set of attributes unless
  16866.  otherwise specified. The default attributes are set with the CODE statement.
  16867.  
  16868.  There are a few other optional parameters that are allowed (but ignored)
  16869.  in the CODE statement for compatibility with Windows and the Presentation
  16870.  Manager.
  16871.  
  16872.  The [load] parameter indicates whether you want the segment
  16873.  automatically loaded upon DLL invocation (PRELOAD) or to wait until the
  16874.  segment is actually accessed with a call (LOADONCALL, the default). In an
  16875.  application with large areas of the code that might never be called, you
  16876.  no longer need to load those library calls into memory all at once. For
  16877.  example, if you had a very large error recovery routine that would only
  16878.  get called once in a great while, you wouldn't need to give up the memory
  16879.  space it required until the error occurred. Another example would be having
  16880.  many levels of help menus that are rarely used──a perfect fit for DLLs.
  16881.  
  16882.  When a LOADONCALL routine is called for the first time, it is loaded
  16883.  automatically and will stay loaded in memory unless it is marked as
  16884.  discardable. That means that its associated memory can be freed as required
  16885.  with fresh copies loaded from disk.
  16886.  
  16887.  If you use the [executeonly] option to specify that other processes
  16888.  cannot read this segment, then, even though the LDT marks the selector as
  16889.  globally accessible, it cannot be read or treated like a data segment
  16890.  selector by any process without the appropriate privilege level. The
  16891.  default, EXECUTEREAD, allows the memory allocated to this selector to be
  16892.  read for purposes other than execution.
  16893.  
  16894.  It is important to note that routines containing such items as a
  16895.  switch/case statement cannot be made EXECUTEONLY since a portion of the
  16896.  code space is used as data space for storage of the switch label table.
  16897.  
  16898.  Only code segments with a high enough privilege level can access the
  16899.  hardware directly. You can use the [iopl] parameter to specify that a
  16900.  segment has this ability. The default (NOIOPL) makes sense; unless
  16901.  otherwise specified, an attempt to access the hardware directly, for
  16902.  example the com port, will cause an immediate fault. When you allow a
  16903.  segment to access hardware directly, include the IOPL parameter in the
  16904.  CODE line. It is probably best to specify IOPL only when required. OS/2
  16905.  still requires that you make a system call to request the privilege of
  16906.  hardware access.
  16907.  
  16908.  The way privilege level functions in OS/2 is an important factor when
  16909.  trying to understand the implications of the IOPL parameter. The Intel
  16910.  programmer's reference manuals for the 80286 and 80386 provide a detailed
  16911.  description of the 80286/80386 IOPL levels.
  16912.  
  16913.  The 80286/80386 chip prohibits direct transitions between code segments
  16914.  with different levels of privilege. The default privilege level for
  16915.  application code in OS/2 is Ring 3. Ring 2 code segments can access
  16916.  hardware directly. The only way to transfer from one privilege level to
  16917.  another is through what is known as a call gate, which has a specific
  16918.  selector type in the LDT and an actual "destination selector," the
  16919.  selector belonging to the actual code segment of the privileged call.
  16920.  Also, the gateway has its own attendant privilege level and can only be
  16921.  called by code segments of the same privilege level.
  16922.  
  16923.  Conceptually, when a call is made to a privileged routine, it passes
  16924.  through the call gate before passing to the privileged routine. Since the
  16925.  only way through the call gate would be with either a CALL instruction
  16926.  (going into the routine) or a RET (coming back from the routine), the call
  16927.  gateway provides extra code security, but at the cost of some additional
  16928.  hardware overhead. Each transition via a gateway causes parameters on
  16929.  the stack to be copied to a new stack──another interesting security
  16930.  feature of the 80286, since a program with a lower privilege level could
  16931.  manipulate the return address on the stack otherwise.
  16932.  
  16933.  A routine utilizing the IOPL parameter uses up an additional slot in the
  16934.  LDT table. Although the LDT table has 8Kb of possible entries in it-each
  16935.  LDT entry takes up 8 bytes, so an entire segment has been allocated to
  16936.  the LDT in OS/2-5Kb of those are reserved for all shared segments
  16937.  throughout the entire system, including the selector space used for DLL
  16938.  selectors. This leaves you with about 3Kb of LDT entries for private use,
  16939.  which is probably enough for the foreseeable future. This has been
  16940.  modified to a 3-to-1 shared-to-private ratio in the next release of OS/2.
  16941.  
  16942.  The final parameter in the CODE statement allows you to specify whether
  16943.  the segment is a NONCONFORMING or a CONFORMING segment. This also deals
  16944.  with the IOPL privilege level and can be pretty confusing at first.
  16945.  Consider it to be the inverse of the gateway approach. Normally, a segment
  16946.  will execute with the privilege level of the calling segment. However,
  16947.  there are times when this might not be appropriate; consider a Ring 3
  16948.  communications protocol checking routine called from a Ring 0 device
  16949.  driver. In this situation, you might not want to allow the protocol
  16950.  checker to operate with the higher privilege of its calling segment. The
  16951.  default case, the NONCONFORMING parameter, would cause the Ring 3 routine
  16952.  to execute at Ring 3. Set to CONFORMING, it would execute at the privilege
  16953.  level of the routine calling it, the device driver running at Ring 0.
  16954.  
  16955.  
  16956.  Data Space Definitions
  16957.  
  16958.  Just as code segments have a method of setting default parameters, the
  16959.  data segments also allow certain parameters to be set with the DATA
  16960.  statement, which shares some of its parameter list with the CODE
  16961.  statement. This makes a great deal of sense because these parameters
  16962.  describe how to make the default settings for each data selector in the
  16963.  LDT. The format of the DATA statement is therefore very similar to the
  16964.  CODE statement.
  16965.  
  16966.  As before, [load] indicates whether the data segment should be loaded upon
  16967.  first invocation or when the first access to the selector's address is
  16968.  made. The default condition is LOADONCALL; however, you can specify
  16969.  invocations of load with PRELOAD.
  16970.  
  16971.  The [readonly] parameter helps you to determine whether the data segment
  16972.  can be written into with the default parameter of READWRITE or should be
  16973.  protected against write access (READONLY). Attempts to write to a READONLY
  16974.  segment cause a hardware fault.
  16975.  
  16976.  You use [instance] to specify whether or not the data segment, the DGROUP
  16977.  data segment in most cases, should be automatically allocated upon
  16978.  invocation, and if so, whether there should be one copy allocated for
  16979.  the entire DLL (SINGLE, the default setting for DLLs) or each instance of
  16980.  DLL use should have its own automatic data segment allocated (MULTIPLE,
  16981.  the default setting for applications). If no automatic allocation is
  16982.  required, then the parameter should be set to NONE.
  16983.  
  16984.  Each data segment can have its own IOPL level, which allows you to set the
  16985.  minimum privilege level required to access this data segment. Setting the
  16986.  [iopl] parameter to IOPL means that only Ring 2 and higher privileged
  16987.  levels are granted access to the data segment. The default, NOIOPL,
  16988.  enables Ring 3 code segment routines to have access to the data affiliated
  16989.  with the data segment. This allows you to create an interesting interface
  16990.  between IOPL and NOIOPL segments through common shared memory, like
  16991.  passing a message through a keyhole.
  16992.  
  16993.  Finally, [shared] allows you to determine whether or not a data segment
  16994.  marked as a READWRITE segment can be shared among different tasks. If it
  16995.  is marked as shareable, then only one segment is allocated at load time,
  16996.  and any process with a privilege level sufficient to write to it can do
  16997.  so. The default, NONSHARED, does not permit write access to a common data
  16998.  segment and causes a separate copy to be loaded for each instance. If a
  16999.  data segment is marked as READONLY, then by definition it is shareable.
  17000.  
  17001.  
  17002.  Segment Parameters
  17003.  
  17004.  Unless otherwise specified, code and data segments have the attributes
  17005.  you set in the CODE and DATA statements, or their predefined default
  17006.  values if you don't describe them. However, using the SEGMENTS statement,
  17007.  you can specify the individual characteristics for a given named segment.
  17008.  
  17009.  The [CLASS 'classname'] parameter is an option that lets you specify that
  17010.  the <segmentname> parameter, which is required, be assigned to the class
  17011.  specified. If you don't specify a classname, then the CODE classname will
  17012.  be assigned to code segments and "unclassed" data segments will be
  17013.  assigned to the DGROUP classname, or whatever was specified in the
  17014.  appropriate DATA group definition for the object/segment.
  17015.  
  17016.  
  17017.  EXPORTS Statement
  17018.  
  17019.  The EXPORTS statement is the only method of informing the outside world
  17020.  about the routines of the DLL and is applicable only to DLLs. Unless
  17021.  specified by inclusion in the EXPORTS section of the DEF file, a DLL routine
  17022.  is invisible to applications.
  17023.  
  17024.  A name used internally within the DLL need not be the name the application
  17025.  knows the routine by; you can easily make the outside name different from
  17026.  the internal name. This gives you a class of functions serving a similar
  17027.  purpose and then lets you categorize them with a meaningful prefix.
  17028.  
  17029.  If you want, you can allow access to the function by its ordinal (or the
  17030.  routines library "slot" number) instead of by its name, by specifying the
  17031.  desired ordinal, obviously unique for the DLL, preceded by an at (@)
  17032.  sign. If you do, lookups will be faster at load time, and less space will
  17033.  be required for the in-memory search list.
  17034.  
  17035.  There is a drawback to using ordinals, though; once you pick an ordinal
  17036.  number, you're stuck with it. This makes debugging a bit harder. Of
  17037.  course, the ordinal approach is good for DLLs whose internal structure
  17038.  you'd rather keep secret.
  17039.  
  17040.  If you do decide on the [@ordinal] option, then you may have to consider
  17041.  using the [RESIDENTNAME] option as well; normally, if an ordinal is used,
  17042.  OS/2 does not keep the specified external name available. If you don't use
  17043.  the ordinal parameter, OS/2 keeps the name resident in its search tables.
  17044.  
  17045.  If you include the use of any privileged functions in your routine, you
  17046.  have to let the linker know how many words to reserve for parameter
  17047.  copying by using the [pwords] variable. This information is later passed
  17048.  to the loader for construction of call gates. Since a calling task will
  17049.  have its own parameters copied as it passes through the gateway, you would
  17050.  have to reserve that space beforehand.
  17051.  
  17052.  
  17053.  IMPORTS Statement
  17054.  
  17055.  The IMPORTS section enables you to specify which external DLL routines you
  17056.  need in your application.
  17057.  
  17058.  Again, like the EXPORTS lines, you can specify a name your routine uses
  17059.  when it is trying to resolve external routines. You could, therefore,
  17060.  create a debugging DLL and a normal DLL and be able to link between them
  17061.  only by changing the <modulename> or the <entryname> associated with the
  17062.  named routine. The name of the application or DLL containing the desired
  17063.  <entryname> is the <modulename> specified in the EXPORTS statement for the
  17064.  DLL. The <entryname> can also be an ordinal number.
  17065.  
  17066.  If the optional [name=] parameter is not specified, then the default name
  17067.  by which the routine will be known will be the same as <entryname>. You
  17068.  must specify an internal name, however, if you've specified an ordinal
  17069.  number instead of an <entryname>.
  17070.  
  17071.  You can include other statements in the DEF file(s). They are summarized
  17072.  in Figure 5.
  17073.  
  17074.  
  17075.  Using the DEF Files
  17076.  
  17077.  There are two specific ways to use the DEF files: first, just include
  17078.  them on the command line to the linker, and second, pass them to the
  17079.  Import Library Manager utility, IMPLIB.
  17080.  
  17081.  IMPLIB is a standard part of the developer's toolkit in OS/2. If you're
  17082.  creating a DLL and the application to use that DLL, you don't necessarily
  17083.  need IMPLIB, since you can create the EXPORT and IMPORT library definition
  17084.  files as you desire. However, if you're creating a DLL for other
  17085.  applications to use, such as a commercial functions library or a
  17086.  replacement for a product that already exists, then IMPLIB should be part
  17087.  of your development cycle.
  17088.  
  17089.  IMPLIB takes one or more definition files for input and then produces what
  17090.  appears to be a simple LIB file for output. This allows you to include the
  17091.  LIB file in the link step. Assuming that you had two DLLs, called
  17092.  COM_INP.DLL and COM_OUT.DLL, each with their associated DEF files, you
  17093.  could create an IMPLIB statement such as the one shown in Figure 6.
  17094.  
  17095.  Then, simply distribute the COM_STUF.LIB and the two DLLs, keeping the
  17096.  internal details of the DLLs to yourself. Special versions of these import
  17097.  library files can allow for different public definitions of your DLL,
  17098.  for instance, one for production, and one for debugging.
  17099.  
  17100.  
  17101.  A DLL Example
  17102.  
  17103.  In attempting to create a DLL, I ran into a number of difficulties,
  17104.  some of which cannot, by the very nature of the DLL and multitasking
  17105.  software, be resolved. Deadlocks can occur in DLLs just as they do in
  17106.  other types of software.
  17107.  
  17108.  Think of the requirements of a multisession process such as a "chat"
  17109.  facility: multiple copies of the same process running, each of which
  17110.  occasionally generates a message, which is added to some internal queue.
  17111.  Each message generated must be collected by all other processes before it
  17112.  can be erased from the queue of outstanding messages, and each such message
  17113.  must eventually be displayed by each process. Finally, each process must be
  17114.  able to login or logout from the chat session and must have a unique
  17115.  identifier.
  17116.  
  17117.  I've designed such a facility, called DLL_CHAT (see Figure 7 for the code
  17118.  listings), as a method of demonstrating some of the unique abilities and
  17119.  problems that occur when using a DLL as the glue to hold a multiprocess
  17120.  concept like this together. It may not be useful on a single-screen
  17121.  machine, but if the output were going to a number of communications
  17122.  ports, it would be.
  17123.  
  17124.  You should note that all the problems inherent with this code design can
  17125.  be easily solved by including the OS/2 system resource of queues. However,
  17126.  that approach wasn't used, primarily because it wouldn't have required
  17127.  DLLs and wouldn't have been as efficient, either.
  17128.  
  17129.  
  17130.  Stepwise Design
  17131.  
  17132.  One advantage of using DLLs in this application is the ability not only to
  17133.  have private and shared memory, but for separately compiled and executed
  17134.  tasks to utilize the same code simultaneously. There is nothing to prevent
  17135.  users of DLL_CHAT from following my coding conventions and creating their
  17136.  own user-friendly interfaces──the bane of spiffy-concept designers. In
  17137.  fact, there is no reason why differently designed front ends couldn't be
  17138.  used for each session. Starting with this concept, I designed this code
  17139.  with most of the capabilities in the DLL.
  17140.  
  17141.  One ability of my DLL, called CHATLIB (see Figure 8 for code listings), is
  17142.  to provide for initialization code that is executed either on the
  17143.  first invocation of the DLL only, or on each invocation. This
  17144.  initialization routine is called before the process itself starts to
  17145.  run. This DLL only calls its initialization routine the first time, so its
  17146.  EXPORT file contains the INITGLOBAL parameter. Since this is the default
  17147.  condition, you can exclude it if you wish. The routine I use in this DLL
  17148.  is simple, setting certain default conditions and allocating some
  17149.  required queue space.
  17150.  
  17151.  First, the login procedure has to advise the library code that another
  17152.  consumer and provider of messages has suddenly appeared. To make things
  17153.  easier, the login procedure returns some user identifier to the process.
  17154.  It is useful to include an ID when generating new messages, when
  17155.  consuming old ones, and, of course, when logging out.
  17156.  
  17157.  The login procedure could have been part of the initialization
  17158.  routine, which would have been of type INITINSTANCE. However, since the
  17159.  initialization routine is run before the application gets control, I
  17160.  felt uneasy about logging in before the main routine had even been
  17161.  reached. As you can see, the initialization routine is used for things
  17162.  that I consider to have a "system" nature, while logging in seems to be
  17163.  more of an "application" task. However, this is only a personal
  17164.  sentiment.
  17165.  
  17166.  When the DLL sees the login, it allocates and assigns whatever global and
  17167.  local objects and structures are necessary for the new process. Where the
  17168.  actual allocations of memory would be made had to be decided in the
  17169.  design, since the memory could be allocated either in the DLL (becoming,
  17170.  in essence, a hidden object from the client code) or in the per-process
  17171.  code itself. There are advantages to having a DLL routine allocate memory
  17172.  that is globally accessible to all processes but that only the DLL
  17173.  routines know about.
  17174.  
  17175.  Also, a login causes each message already in the queue to appear unread to
  17176.  the newly logged-in task. Later, when requests are made for an
  17177.  outstanding and unread message, these messages will be returned.
  17178.  
  17179.  The general design of the DLL causes a sharing of the cleanup task on each
  17180.  call to the get-a-message routine. When a message is passed to the DLL, it
  17181.  is added to a queue, a structure that includes a flag word with one bit
  17182.  for each session. A mask word with a set bit for each empty task slot is
  17183.  used for the initial value of this flag word. An "or" operation is then
  17184.  used to obtain the current task ID, allowing the sender of the message to
  17185.  indicate that it has already received the message.
  17186.  
  17187.  When a process fetches a new message, it sets the bit in the flag word to
  17188.  show that it has done so. Then, when that flag indicates that all
  17189.  processes have received a copy of the message, the message can be removed
  17190.  from the global queue. Therefore, each process has to be able to
  17191.  manipulate that queue directly or must call a routine that has that
  17192.  ability. I've opted for a more modular design; using a routine to
  17193.  specifically remove the message from the queue, or to add a message to
  17194.  the queue, allows me to isolate the queue itself.
  17195.  
  17196.  Although the queue resides in global memory at this point, perhaps in the
  17197.  future it might reside on some node on a network or some memory device
  17198.  that might require a higher privilege level. Therefore, isolating the
  17199.  routine that physically modifies the queues is a good idea.
  17200.  
  17201.  Since there isn't a human attached to each session, each session sends a
  17202.  message only after a random amount of time has passed. And, just to keep
  17203.  things interesting, there is a suitable sleep period while the imaginary
  17204.  typist is entering his or her message, letting messages build up in the
  17205.  queue. Whenever the sender is not typing or sending a message, it is
  17206.  executing a loop that constantly seeks the outstanding message count.
  17207.  Blocking on a null message count would prohibit the sender from sending a
  17208.  message. Of course, OS/2 lets you have two different threads, one of which
  17209.  could block on a null message count within the DLL, but that is outside
  17210.  the scope of this article.
  17211.  
  17212.  Displaying of received messages occurs on a per-process basis. This
  17213.  could cause problems if the display takes place when the session is not
  17214.  the current foreground session; messages could scroll off the virtual
  17215.  screen while the process is in the background, an unacceptable condition
  17216.  in a real chat system.
  17217.  
  17218.  If a message is to be displayed but the session is not the foreground
  17219.  session, it is stored in an internal display queue. Normally, this queue
  17220.  would be a linked list of calloc'd memory. For DLL_CHAT, a short
  17221.  character array was used, one which would fill up quickly. Eventually,
  17222.  when it does fill up, it will stop fetching messages from the DLL queue.
  17223.  Since the DLL queue is constantly fed by all of the sessions, it too will
  17224.  fill up.
  17225.  
  17226.  Another session will then block when attempting to add a message to the
  17227.  queue. This condition can escalate until all sessions are blocked.
  17228.  Therefore, before any session sends a message, it checks to determine if
  17229.  room exists in the queue.
  17230.  
  17231.  OS/2 is a multitasking operating system, so a routine must not be
  17232.  interrupted between the time it determines there is room on the queue and
  17233.  actually adding the message to the queue. However, you don't want to
  17234.  starve other sessions, especially not those related in any way to
  17235.  DLL_CHAT.
  17236.  
  17237.  To prevent this, DLL_CHAT uses a globally accessible system semaphore and
  17238.  assigns the semaphore immediately upon entry to any collision-sensitive
  17239.  routine. Other processes trying to enter one of these routines would block
  17240.  on this flag and wait for it to free up or for a certain amount of time to
  17241.  pass. If the flag didn't change within the specified time-out period, then
  17242.  an error condition would be returned to the calling task.
  17243.  
  17244.  Finally, there is the logout routine. When the session gets a quit or exit
  17245.  command from the keyboard, it simply exits. However, as an intrinsic part
  17246.  of exiting, all DosExitList routines are called. The exit routine, in
  17247.  turn, calls the DLL's logout routine, which then sets the semaphore and
  17248.  proceeds to loop through the outstanding message list. For each
  17249.  outstanding message, it sets the flag as if the process had already
  17250.  received the message. After each flag word has been set, it is examined to
  17251.  determine if it has been read by all processes; if so, it is removed from
  17252.  the queue.
  17253.  
  17254.  Each message on the queue is a member of a linked list, and its memory is
  17255.  allocated from the global memory pool. When removing a message from the
  17256.  queue, the pointers of the other messages it points to are modified to
  17257.  point to each other, and then the memory is deallocated.
  17258.  
  17259.  
  17260.  Caveats and Warnings
  17261.  
  17262.  There are a few things you have to be aware of when you're designing your
  17263.  DLLs. I mentioned the extraordinary lengths I went to in the original
  17264.  design of DLL_CHAT to assure that certain areas of the code are
  17265.  protected against two competing tasks attempting to access them at
  17266.  once. This is a problem inherent in any multiprocessing system.
  17267.  Typically, the problem is called "reentrancy," that is, a piece of code
  17268.  being entered by a calling process before another process has finished its
  17269.  call. The actual definition of reentrancy is much stricter than this, but
  17270.  this is good enough for our purposes.
  17271.  
  17272.  Utilizing semaphores is effective in most circumstances. However, the
  17273.  method I chose would not work best without some of the special hooks OS/2
  17274.  provides for the safe use of semaphores. Consider what happens if the
  17275.  session currently executing a semaphored routine happens to be interrupted
  17276.  and then gets killed by some high-priority event after it sets the
  17277.  semaphore, but before the semaphore is released. Such an event could be
  17278.  as normal as a keystroke being entered, or, if attached to a comm port,
  17279.  the modem losing carrier, or something even more esoteric. There is no
  17280.  guarantee that it will return to where it left off, but if it doesn't
  17281.  return and finish the routine, the semaphore will forever be marked as in
  17282.  use.
  17283.  
  17284.  OS/2 does offer an alternative if you use one of the system semaphores.
  17285.  The semaphore is created with a DosOpenSem call, which returns a
  17286.  semaphore handle, similar to a file handle. By using other semaphore
  17287.  calls, a process can effectively keep reentrancy from occurring. In the
  17288.  event that a process owning the semaphore at that time, and therefore
  17289.  blocking others waiting on it, gets killed for some reason, even
  17290.  unintentionally, the system will effectively call DosCloseSem, which
  17291.  clears the semaphore if set and restores it as a system resource if there
  17292.  are no other references to it.
  17293.  
  17294.  Each individual session of DLL_CHAT uses a few resources, which I wanted
  17295.  to make sure were properly cleaned up as a session exits. I used the OS/2
  17296.  system call DosExitList to add some specific routines to each session's
  17297.  exit list, the list of routines that OS/2 executes on my behalf between
  17298.  the time the client program dies and the time it is buried. Currently,
  17299.  the exit routine simply calls the logout procedure, which in turn resets
  17300.  the systemwide flag word and the bits in each message and finally cleans
  17301.  up the message base and any outstanding semaphores.
  17302.  
  17303.  When designing a DLL, you should always think of worst-case scenarios,
  17304.  such as what would happen if this line of code were running while ten
  17305.  other processes were running those ten different lines of code. Since you
  17306.  cannot effectively control what the other processes are doing as they
  17307.  start to execute common areas of code, it is better to design the code as
  17308.  modularly as possible and be sure to semaphore around areas sensitive to
  17309.  multitasking occurring at just the wrong time──chances are that it will.
  17310.  
  17311.  Remember that you must not only program defensively against other
  17312.  processes using the DLL routines and their attendant data; if you opt to
  17313.  use OS/2 threads, you have to protect against their reentrant use of the
  17314.  DLL routines. Most of these considerations regarding DLLs can also be
  17315.  important when designing a threaded program.
  17316.  
  17317.  When considering unanticipated or asynchronous interruptions, you
  17318.  should think about signal catching and about not doing it in a DLL. If
  17319.  you're going to use the system to set a routine to catch a particular
  17320.  asynchronous event, such as program termination or Ctrl-C trapping
  17321.  performed with the DosSetSigHandler system call, doing it in the DLL can
  17322.  be dangerous. This is a general concept that can be applied toward
  17323.  program design not just in OS/2 but in any multiprocessing operating
  17324.  system.
  17325.  
  17326.  The concept of resource plays a critical role here. Who owns the resource
  17327.  of a signal catcher──an interrupt vector or another system facility in a
  17328.  multiprocessing operating system? Generally, the operating system
  17329.  itself owns the resource. When you take over such a system resource for
  17330.  your own purposes, you must remember that other processes might cause the
  17331.  resource to be used──and that you have no control over the event that
  17332.  resulted in the use of the resource.
  17333.  
  17334.  Normally, this would not be much of a problem, since the state of the
  17335.  resource can be saved before you take it over and then restored when
  17336.  you're done with it. But with at least one system facility, DosError,
  17337.  which allows a process to suspend hardware error processing, the state
  17338.  cannot be preserved for the next call or use. Thus, the state of the
  17339.  system can get pretty confused.
  17340.  
  17341.  Similarly, it is probably a good idea to avoid DosSetVect, which lets your
  17342.  exception handler be called when certain conditions, such as attempts to
  17343.  execute an illegal opcode, occur. Generally, if there is a possibility
  17344.  that some state in the operating system cannot be preserved owing to your
  17345.  taking over a system resource or facility, you should consider designing
  17346.  your code differently.
  17347.  
  17348.  If you must include such calls in your code, be sure to isolate thoroughly
  17349.  those portions of the code from other client members of the DLLs, and to
  17350.  preserve all other aspects of your process state. Be sure to terminate
  17351.  normally, too, not in a unique way, since DLLs have some special
  17352.  characteristics that are properly dealt with in automatic exit list
  17353.  processing upon client death.
  17354.  
  17355.  
  17356.  Design Considerations
  17357.  
  17358.  When designing your program to use DLLs, you have to be careful about a
  17359.  few things in your initial program design. First, access to all DLL
  17360.  routines is through a far call. So, although you can use the small memory
  17361.  models in the client section of your code and in the DLL itself, the
  17362.  external definition of the DLL routines must indicate it is a far routine.
  17363.  As such, the routine itself must also indicate in its prototype that it
  17364.  is a far routine; or else the CALL and RET statement types won't match.
  17365.  
  17366.  Remember to design your call to a DLL routine and the routine itself using
  17367.  the Pascal calling convention instead of the C calling convention. Although
  17368.  this is not a strict requirement (however, both calling sequences must
  17369.  match), it's a good idea for several reasons. First, it's required if you're
  17370.  using the routine as an IOPL gate. Also, it's more efficient, since the
  17371.  stack restore takes place only once in the called routine itself, and more
  17372.  languages support the Pascal convention, allowing your DLL to be used by
  17373.  these other languages.
  17374.  
  17375.  What about the data allocated in the DLL? That, too, must be addressed as
  17376.  far data from the client routines. Locally, within the DLL, it can be
  17377.  addressed as near or far as required.
  17378.  
  17379.  Before your client code ever executes, the initialization routine for the
  17380.  DLL will already have executed. Expecting any initialization by the main
  17381.  routine in your client code would be premature. Therefore, your DLL
  17382.  initialization code should only access data within the DLL itself, since
  17383.  the startup code may not even have allocated memory as of yet. You can
  17384.  find out a great deal when the loader calls your initialization
  17385.  routine, including the handle by which the DLL has been opened as well as
  17386.  the size of the heap in the calling EXE program. You can find more
  17387.  information on this in the OS/2 Programmer's Guide.
  17388.  
  17389.  What of the differences between global and instance data items? They can
  17390.  be confusing, since each DLL module has no easy method to determine
  17391.  whether the data object it is using is a private object or one common to
  17392.  all tasks. This can be tricky, since many programmers routinely use
  17393.  temporary pointers to objects that they place in global data space
  17394.  instead of allocating it on the stack for local use.
  17395.  
  17396.  It is important to recognize the differences between global data, such as
  17397.  items defined and allocated outside the scope of any C routine, and
  17398.  globally accessible data. In the first case, it is really local data, that
  17399.  is, data local to the client process itself and not accessible to other
  17400.  clients of the DLL. In the second case, it is accessible to all clients of
  17401.  the DLL, and that can fool you if you're not careful. You must be sure
  17402.  that globally accessible data items don't change value when you're not
  17403.  looking. Keeping items on a local stack frame is probably the safest bet.
  17404.  Items that are kept around without changing value are best placed in
  17405.  private client data space.
  17406.  
  17407.  You can easily indicate through the DATA statement in the EXPORT file
  17408.  which data segments you want to be allocated on a private per-client
  17409.  basis and which you want to be globally accessible. If a segment is marked
  17410.  as READONLY, then it is globally accessible. The Microsoft(R) C data group
  17411.  named CONST should always be marked as READONLY; this allows for only one
  17412.  copy of literal strings to be loaded for the entire DLL.
  17413.  
  17414.  
  17415.  Using Microsoft C
  17416.  
  17417.  There were some rumors floating around for a while that it was impossible
  17418.  to create DLLs with a C compiler because the stack segment (SS) did not
  17419.  equal the data segment (DS) upon entry into a DLL──clearly a run-time issue
  17420.  and not a compiler problem. Since the library has many routines that
  17421.  expect them to be equal, it appeared at first that creating DLLs in C was
  17422.  expressly forbidden, but this is not the case.
  17423.  
  17424.  There are some specific enhancements available in the Microsoft C Version
  17425.  5.1 compiler that make writing C DLLs very easy.
  17426.  
  17427.  A new pragma, pragma data_seg, allows you to specify for any function that
  17428.  later loads its own data segment exactly which data segment to use. By
  17429.  specifying the data segment as
  17430.  
  17431.    #pragma data_seg (segment_name)
  17432.  
  17433.  you not only facilitate using DLLs, but you have more control over which
  17434.  data segment all initialized static and global data will reside in. The
  17435.  default data segment name if you don't specify one is the name used by
  17436.  DGROUP, which depends on the memory model you use.
  17437.  
  17438.  This is half of the solution of which data segment to use in the DLL. The
  17439.  other half is to specify the called function as one that uses the
  17440.  previously saved data segment with the _loadds keyword. Upon entry into a
  17441.  _loadds function, the current DS register is saved, the last one specified
  17442.  by the pragma data_seg directive is written into it, the function is
  17443.  executed, and the saved DS is restored upon exit. This is not such a new
  17444.  concept, since you've been able to use /Au as a compiler option for some
  17445.  time now, but this lets you specify some capability on a function-by-
  17446.  function basis.
  17447.  
  17448.  In order for the compiler to know, in advance, that the routine is going
  17449.  to be part of a dynamic-link library, the new keyword _export has been
  17450.  added. In particular, if the function has an I/O privilege level (IOPL)
  17451.  associated with it, then the number of words to reserve for the privilege
  17452.  level transition stack copy operation can be easily calculated at compile
  17453.  time if _export is used. In fact, if you use _export as part of your
  17454.  function definition, the number of words to reserve as indicated in the
  17455.  DEF file is ignored.
  17456.  
  17457.  When setting up the various data segments into their constituent types
  17458.  (SHARED and READONLY, for instance), you should also take a look at the
  17459.  map file produced from the link; some additional segments might be created
  17460.  that you hadn't thought about. In particular, some NULL segments are
  17461.  created for each group as _CONST and _BSS. So as not to confuse the
  17462.  linker, each member of the group should be specified within the SEGMENTS
  17463.  section of the EXPORT DEF file, and you need only mention the special
  17464.  segments, those with attributes different from the default setting of the
  17465.  DATA statement.
  17466.  
  17467.  
  17468.  Creating the Initialization Routine for Your C DLL
  17469.  
  17470.  Remember that the DLL, once passed through the linker, looks much like an
  17471.  EXE file. In fact, the same load routine used for your own client module
  17472.  loads the DLL itself. And, if you've defined an initialization routine
  17473.  within the DLL, it will be executed almost as if it were a standalone
  17474.  routine; it is called immediately after the DLL is loaded, and then only
  17475.  called upon subsequent loads of the DLL, if you specify so.
  17476.  
  17477.  You can easily tell the loader where the initialization routine is located
  17478.  by including a small assembly language routine as part of your DLL and
  17479.  linking it into the DLL. In fact, it is probably not a bad idea to have a
  17480.  module similar to the one in Figure 9 and to always give your DLL
  17481.  initialization routine the same name. The secret of the initialization
  17482.  routine is just that the only program the loader will find is the one that
  17483.  is addressed by the END START directive.
  17484.  
  17485.  The MSC compiler throws a small monkey wrench in your path. Meaning to be
  17486.  helpful, the compiler throws the _acrtused variable into each object
  17487.  module. This forces the linker to include some of the startup routines
  17488.  from the C run-time library into the eventual output of the linker, which
  17489.  the compiler thought was going to be a normal EXE file. To prevent this
  17490.  code from being loaded into your DLL, you should define the variable
  17491.  yourself, as external data:
  17492.  
  17493.    int _acrtused = 0x1234;
  17494.  
  17495.  or some number of particular meaning to you.
  17496.  
  17497.  Also, to have global data items show up in the named segment for the
  17498.  particular object module that you are linking, it should be initialized,
  17499.  declared as static, or both. Finally, you could also allocate static or
  17500.  global data using the keyword const along with the appropriate compiler
  17501.  switches.
  17502.  
  17503.  When writing your own DLL, you should use the -Gs switch on the MSC
  17504.  compiler to disable stack checking. Aside from the slight gain in
  17505.  efficiency──somewhat smaller code and one less function call per
  17506.  function──the DLL requires this since the stack segment is different for
  17507.  each client process and the size of the stack may vary on a per-client
  17508.  basis as well. In the few places where you really need stack checking, MSC
  17509.  provides you with an abundant set of pragmas and routines.
  17510.  
  17511.  MSC 5.1 includes some welcome additions to the run-time libraries
  17512.  package. These include a library that provides multithread support, a
  17513.  run-time library that is statically linked with your routines to provide
  17514.  for a DLL written almost entirely in C, and a run-time library that, in
  17515.  and of itself, is a dynamically linked library and works with EXE and
  17516.  other DLL libraries. The new DLL run-time libraries also allow you to use
  17517.  any of the functions that you've grown accustomed to.
  17518.  
  17519.  There are some subtle differences in the way certain things are handled
  17520.  in a multitasking/multiprocessing environment, but they are hidden
  17521.  from view in some of the header files. The errno variable, for example, is
  17522.  now a macro that translates into a function call. This is done because a
  17523.  table must now be used in the functions of the run-time library to enable
  17524.  a single run-time package to handle errors from multiple sources.
  17525.  
  17526.  By the look of things and how they operate, it is most likely safe to
  17527.  assume that semaphores were used throughout the library──to keep a call
  17528.  using a globally accessible variable from being clobbered by two client
  17529.  processes trying to use it simultaneously. This forces a heavy overhead in
  17530.  system calls to frequently called routines, but there is little choice in
  17531.  this matter. Remember that the libraries had to be written under a worst-
  17532.  case scenario, and you pay a penalty in speed and efficiency for the
  17533.  safety inherent in putting semaphores around the dangerous routines (see
  17534.  Figure 10).
  17535.  
  17536.  Certain idiosyncracies do exist in the C libraries, however. For example,
  17537.  the memory allocation functions alloc, malloc, and calloc follow the
  17538.  ANSI calling convention. However, the ANSI calling convention does not
  17539.  allow you to specify whether memory should be private or shared between
  17540.  different processes. Microsoft has yet to add functions to the library
  17541.  that allow this; this is the reason for the rather convoluted routines
  17542.  used in DLL_CHAT for dealing with allocating memory in a shared way.
  17543.  
  17544.  
  17545.  Conclusion
  17546.  
  17547.  With the introduction of DLLs in OS/2, a new programming environment has
  17548.  been created. Much like Windows and Presentation Manager programming, it
  17549.  has its own strict rules, but they make a great deal of sense once the
  17550.  underlying design concept and limits of the chip set and the appropriate
  17551.  portions of OS/2 are understood.
  17552.  
  17553.  You can avoid a lot of these sticky problems by piece-at-a-time
  17554.  programming: get as much of your program to work using routines in a more
  17555.  normal library using the library utilities and then move the routines out
  17556.  into a DLL. By adding the functionality and safeguards required for shared
  17557.  memory access between sessions and reentrancy problems, you can easily
  17558.  create a program that uses up less disk space, less memory space, and
  17559.  allows for interprocess communication in whatever manner you wish to
  17560.  design-not bad features for a new operating system to be written around.
  17561.  
  17562.  And once you've been through the maze of twisty little passages, it isn't
  17563.  so hard the next time to get through it rapidly and collect that treasure.
  17564.  The secret is just knowing a couple of key phrases and thinking ahead
  17565.  before you enter the maze.
  17566.  
  17567.  
  17568.  Figure 1:  Once a DLL is created, use IMPLIB to create a LIB file from the
  17569.             EXPORT DEF file. The LIB file provides a dynamic-link reference
  17570.             that is built into the EXE file. The OS/2 loader then uses this
  17571.             information to resolve DLL references when the EXE file is
  17572.             executed. By providing both the DLL and LIB files, the internal
  17573.             format of DLL remains hiddenfrom the user of the DLL.
  17574.  
  17575.  Step 1: Create the DLL
  17576.   ┌───────────────────────┬──┐
  17577.   │OBJ Files              │  │                        ╔═════╗
  17578.   └───────────────────────┘  │                    ┌──║ DLL ║
  17579.   ┌───────────────────────┐  │      ╔══════╗      │   ╚═════╝
  17580.   │Static Library         │  ├─────║LINKER╟──────┤
  17581.   └───────────────────────┘  │      ╚══════╝      │   ╔═════╗
  17582.   ┌───────────────────────┐  │                    └──║ MAP ║(Optional)
  17583.   │Export Definition File │  │                        ╚═════╝
  17584.   └───────────────────────┴──┘
  17585.  
  17586.  Step 2: Create the LIB file
  17587.   ┌───────────────────────┐         ╔══════╗          ╔══════════╗
  17588.   │Export Definition File ├────────║IMPLIB╟─────────║ LIB FILE ║
  17589.   └───────────────────────┘         ╚══════╝          ╚══════════╝
  17590.  
  17591.  Step 3: Create an Application that Uses the DLL
  17592.   ┌───────────────────────┬─┐
  17593.   │LIB File               │ │                         ╔═════╗
  17594.   └───────────────────────┘ │                     ┌──║ DLL ║
  17595.   ┌───────────────────────┐ │       ╔══════╗      │   ╚═════╝
  17596.   │Application OBJ Files  │ ├──────║LINKER╟──────┤
  17597.   └───────────────────────┘ │       ╚══════╝      │   ╔═════╗
  17598.   ┌───────────────────────┐ │                     └──║ MAP ║(Optional)
  17599.   │Static Library         │ │                         ╚═════╝
  17600.   └───────────────────────┴─┘
  17601.  
  17602.  Step 4: Load the EXE File and Resolve DLL References
  17603.   ┌─────┬─┐
  17604.   │ DLL │ │
  17605.   └─────┘ │       ╔═══════════╗       ╔═════════════════════════════════╗
  17606.           ├──────║OS/2 LOADER╟──────║ IN MEMORY EXECUTION OF EXE FILE ║
  17607.   ┌─────┐ │       ╚═══════════╝       ╚═════════════════════════════════╝
  17608.   │ EXE │ │
  17609.   └─────┴─┘
  17610.  
  17611.  
  17612.  Figure 2:  Using IMPORT DEFINITION files is a better method for developing
  17613.             your DLL than using LIB files. There is no reason not to use
  17614.             IMPLIB for your finished DLL and its distribution. The EXPORT DEF
  17615.             file allows you to define everything that you need to.
  17616.  
  17617.   ┌──────────────────────────┬─┐
  17618.   │       OBJ Files          │ │
  17619.   └──────────────────────────┘ │
  17620.   ┌──────────────────────────┐ │    ╔═════════╗        ╔═══════╗
  17621.   │      Static Library      │ ├───║  LINK   ╟────┬──║  DLL  ║
  17622.   └──────────────────────────┘ │    ╚═════════╝    │   ╚═══════╝
  17623.   ┌──────────────────────────┐ │                   │   ╔═══════╗
  17624.   │     EXPORT DEF File      │ │                   └──║  MAP  ║(Optional)
  17625.   └──────────────────────────┴─┘                       ╚═══════╝
  17626.   ┌──────────────────────────┬─┐
  17627.   │     IMPORT DEF File      │ │
  17628.   └──────────────────────────┘ │
  17629.   ┌──────────────────────────┐ │    ╔═════════╗        ╔═══════╗
  17630.   │        OBJ Files         │ ├───║  LINK   ╟────┬──║  EXE  ║
  17631.   └──────────────────────────┘ │    ╚═════════╝    │   ╚═══════╝
  17632.   ┌──────────────────────────┐ │                   │   ╔═══════╗
  17633.   │      Static Library      │ │                   └──║  MAP  ║(Optional)
  17634.   └──────────────────────────┴─┘                       ╚═══════╝
  17635.  
  17636.  
  17637.  Figure 3:  A Local Descriptor Table (LDT) contains a list of private
  17638.             descriptors accessible only to the local task. These descriptors
  17639.             can point to data segments, executable segements, call gates, and
  17640.             task gates. There can be at most 8192 descriptors, and each LDT
  17641.             can be, at most, 65,355 bytes long. Note that all LDT descriptors
  17642.             must reside, or be defined, in the Global Descriptor Table.
  17643.  
  17644.                    GDT                       ╔══════════════════════════════╗
  17645.  ┌─────────────────────────────────────┐ ┌──║            LDT(1)            ║
  17646.  │GLOBAL DATA SEGMENT DESCRIPTORS      │ │   ║DATA SEGMENT DESCRIPTORS      ║
  17647.  ├─────────────────────────────────────┤ │   ║EXECUTABLE SEGMENT DESCRIPTORS║
  17648.  │GLOBAL EXECUTABLE SEGMENT DESCRIPTORS│ │   ║CALL GATE DESCRIPTORS         ║
  17649.  ├─────────────────────────────────────┤ │   ║TASK GATE DSCRIPTORS          ║
  17650.  │GLOBAL CALL GATE DESCRIPTORS         │ │   /               :              /
  17651.  ├─────────────────────────────────────┤ │   ║               :              ║
  17652.  │GLOBAL TASK GATE DESCRIPTORS         │ │┌─║            LDT(N)            ║
  17653.  ├─────────────────────────────────────┤ ││  ║DATA SEGMENT DESCRIPTORS      ║
  17654.  │GLOBAL TASK STATE SEGMENT DESCRIPTORS│ ││  ║EXECUTABEL SEGMENT DESCRIPTORS║
  17655.  ├─────────────────────────────────────┤ ││  ║CALL GATE DESCRIPTORS         ║
  17656.  │(LOCAL DESCRIPTOR TABLE DESCRIPTORS) ├─┘│  ║TASK GATE DESCRIPTORS         ║
  17657.  │       LDT(1) DESCRIPTOR             │  │  ╚══════════════════════════════╝
  17658.  /               :                     /  │
  17659.  │       LDT(N) DESCRIPTOR             ├──┘
  17660.  └─────────────────────────────────────┘
  17661.  
  17662.  
  17663.  Figure 4:  DEF File Options and Parameters
  17664.  
  17665.  LIBRARY [name][init_type]
  17666.  
  17667.  NAME [appname][apptype]
  17668.  
  17669.  CODE [load][executeonly][iopl][conforming]
  17670.  
  17671.  DATA [load][readonly][instance][iopl][shared]
  17672.  
  17673.  SEGMENTS <segmentname> [CLASS 'classname']
  17674.  [load][readonlyexecuteonly][iopl][conforming][shared]
  17675.  
  17676.  EXPORTS <name>[=internalname][@ordinal][RESIDENTNAME][pwords]
  17677.  
  17678.  IMPORTS [name=]<modulename>.<entryname>
  17679.  
  17680.  
  17681.  Figure 5:  Additional DEF File Options
  17682.  
  17683.  STUB 'filename'     Allows you to specify the name of a DOS 3.x file to
  17684.                      be run if this file is run under DOS instead of under
  17685.                      OS/2.
  17686.  
  17687.  PROTMODE            Indicates that this file can only be run in protected
  17688.                      mode. An aid to the linker.
  17689.  
  17690.  OLD                 This statement allows you to preserve the names
  17691.                      associated with ordinal numbers in a multi-DLL
  17692.                      environment.
  17693.  
  17694.  REALMODE            The opposite of PROTMODE, this indicates the program
  17695.                      can only be run in real mode, and is an aid to the
  17696.                      linker.
  17697.  
  17698.  EXETYPE             Insures that the specified operating system is the
  17699.                      current one for the program. You can specify OS2,
  17700.                      WINDOWS.
  17701.  
  17702.  HEAPSIZE            Determines how much local heap must be allocated within
  17703.                      the automatic data segment.
  17704.  
  17705.  STACKSIZE           Allows you to specify how much space should be reserved
  17706.                      in the stack segment when the program is run.
  17707.  
  17708.  
  17709.  Figure 6:  A Typical IMPLIB Statement
  17710.  
  17711.  IMPLIB COM_STUF.LIB COM_INP.DEF COM_OUT_DEF
  17712.  
  17713.  
  17714.  Figure 7:
  17715.  
  17716.  ───────────────────────────────────────────────────────────────────────────
  17717.  Also see:
  17718.    An overview of the way DLL_CHAT interfaces to its dynamic-link library
  17719.  ───────────────────────────────────────────────────────────────────────────
  17720.  
  17721.  DEF File Format for DLL_CHAT
  17722.  
  17723.  IMPORTS CHATLIB.login
  17724.  IMPORTS CHATLIB.get_msg_cnt
  17725.  IMPORTS CHATLIB.send_msg
  17726.  IMPORTS CHATLIB.logout
  17727.  IMPORTS CHATLIB.get_msg
  17728.  
  17729.  Header File for DLL_CHAT
  17730.  
  17731.  /* Header file for DLL_CHAT */
  17732.  
  17733.  struct gdtinfoarea{
  17734.      unsigned long    time;
  17735.      unsigned long    milliseconds;
  17736.      unsigned char    hours;
  17737.      unsigned char    minutes;
  17738.      unsigned char    seconds;
  17739.      unsigned char    hundreths;
  17740.      unsigned         timezone;
  17741.      unsigned         timer_interval;
  17742.      unsigned char    day;
  17743.      unsigned char    month;
  17744.      unsigned         year;
  17745.      unsigned char    day_of_week;
  17746.      unsigned char    major_version;
  17747.      unsigned char    minor_version;
  17748.      unsigned char    revision_number;
  17749.      unsigned char    current_screen_group;
  17750.      unsigned char    max_num_of_screengrps;
  17751.      unsigned char    huge_selector_shift_count;
  17752.      unsigned char    protect_mode_indicator;
  17753.      unsigned         foreground_process_id;
  17754.      unsigned char    dynamic_variation_flag;
  17755.      unsigned char    maxwait;
  17756.      unsigned         minimum_timeslice;
  17757.      unsigned         maximum_timeslice;
  17758.      unsigned         boot_drive;
  17759.      unsigned char    reserved[32];
  17760.  };
  17761.  
  17762.  struct ldtinfoarea{
  17763.      unsigned    current_process_pid;
  17764.      unsigned    parent_pid;
  17765.      unsigned    priority_of_current_thread;
  17766.      unsigned    thread_id_of_current_thread;
  17767.      unsigned    screen_group;
  17768.      unsigned    subscreen_group;
  17769.      unsigned    current_process_is_in_fg;
  17770.   };
  17771.  
  17772.  Code Listing for DLL_CHAT
  17773.  
  17774.  /*********************************************************************
  17775.  * DLL_CHAT.C  - A demonstration program using a demo DLL
  17776.  *
  17777.  * (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
  17778.  *
  17779.  * This is the main body of the CHAT program, interfacing with
  17780.  * and calling the DLL as if it were a bunch of routines
  17781.  * available with a far call: which it is!
  17782.  *
  17783.  *
  17784.  * Compile with:
  17785.  * cl /c chat.c
  17786.  * link chat,chat,,slibce+doscalls,chat
  17787.  *
  17788.  */Remember: move the DLL itself into your DLL library directory
  17789.  *********************************************************************
  17790.  
  17791.  #include    <stdio.h>
  17792.  #include    <stdlib.h>
  17793.  #include    "chat.h"
  17794.  
  17795.  #define        TRUE    1
  17796.  #define        FALSE    0
  17797.  #define        OK   TRUE
  17798.  #define        NOT__OK  FALSE
  17799.  
  17800.  #define        MAX_MSG        100
  17801.  #define        MAX_MSG_LEN    80
  17802.  #define        NULLP        (void *)NULL
  17803.  
  17804.  /* The following OS/2 system calls are made in this module: */
  17805.  
  17806.  extern far pascal dosexitlist();
  17807.  extern far pascal dossleep();
  17808.  
  17809.  /* The following DLL system calls are made in this module: */
  17810.  
  17811.  extern far _loadds pascal login();
  17812.  extern far _loadds pascal logout();
  17813.  extern far _loadds pascal get_msg_cnt();
  17814.  extern far _loadds pascal get_msg();
  17815.  extern far _loadds pascal send_msg();
  17816.  
  17817.  
  17818.  /********************************************************************
  17819.  *    This is where the messages are stored, once received and
  17820.  *    formatted. This could probably be replaced easily with a call
  17821.  *    to calloc(), but then we wouldn't have to block on being a
  17822.  *    background process
  17823.  ********************************************************************/
  17824.  
  17825.  char    msg_array[MAX_MSG + 1][MAX_MSG_LEN];
  17826.  
  17827.  /* Must be global so that before_death() can access it to logout */
  17828.  
  17829.  int    my_id = 0;
  17830.  
  17831.  #define    MAX_SLEEP    2
  17832.  
  17833.  /********************************************************************
  17834.  *    before_death()
  17835.  *
  17836.  *    Called the coins are lowered onto the eyes of this invocation
  17837.  *    of CHAT. Any exit will cause this routine to be called. After
  17838.  *    this routine calls the DLL logout procedure, it removes the
  17839.  *    next exitroutine in the exit list.
  17840.  ********************************************************************/
  17841.  
  17842.  void    far    before_death()
  17843.  {
  17844.      logout(my_id);
  17845.      dosexitlist(3, before_death);
  17846.  }
  17847.  
  17848.  /********************************************************************
  17849.  *    main()
  17850.  *
  17851.  *    After logging in (which returns a unique login id), the
  17852.  *    before_death() routine is added onto the exitlist. Then the
  17853.  *    main loop:
  17854.  *
  17855.  *    If there are any messages, read them into out memory buffer
  17856.  *    (provided there is room) and kick up the count. After
  17857.  *    retrieving all the msgs which will fit, call the display
  17858.  *    routine for each msg. Zero the count of messages when done.
  17859.  *
  17860.  *    Every couple of seconds, sleep for a little while (as if a
  17861.  *    human typing on a keyboard), then send the message to all
  17862.  *    other members of CHAT.
  17863.  *
  17864.  *    CHAT can only be exited (in its current form) with a
  17865.  *    control-C or error condition.
  17866.  ********************************************************************/
  17867.  
  17868.  main()
  17869.  {
  17870.  int        msg_cnt = 0;
  17871.  int        msg_id = 0;
  17872.  int        l_cnt;
  17873.  char       tmp_buf[MAX_MSG_LEN];
  17874.  int        mcnt;
  17875.  
  17876.      printf("Logged into CHAT as user:%d\n", my_id = login());
  17877.  
  17878.      dosexitlist(1, before_death);
  17879.  
  17880.      while    (TRUE)
  17881.      {
  17882.          for (m_cnt = get_msg_cnt(my_id); m_cnt ; m_cnt-)
  17883.          {
  17884.              get_msg(my_id, (char far *)tmp_buf);
  17885.              if    (msg_cnt <= MAX_MSG)
  17886.                  sprintf(msg_array[msg_cnt++],"(%d)%s",my_id,tmp_buf);
  17887.      }
  17888.  
  17889.          if    (ok_to_disp())
  17890.          {
  17891.              for (lp_cnt = 0 ; lp_cnt < msg_cnt; lp_cnt++)
  17892.                  disp_msg(msg_array[lp_cnt]);
  17893.              if    (msg_cnt > MAX_MSG)
  17894.                  disp_msg("Looks like you might have lost some")
  17895.                  disp_msg(" messages while you were away\n");
  17896.              msg_cnt = NULL;
  17897.          }
  17898.  
  17899.          if    (rand() % (my_id + 1))
  17900.          {
  17901.              dossleep((long)(rand() % MAX_SLEEP) * 1000L);
  17902.              sprintf(tmp_buf, "Test message #%d from Session #%d\n",
  17903.                      msg_id++, my_id);
  17904.              if    (send_msg(my_id, (char far *)tmp_buf) == NOT_OK)
  17905.                  printf("?Can't send a message....\n");
  17906.          }
  17907.      }
  17908.  }
  17909.  
  17910.  disp_msg(ptr)
  17911.  char    *ptr;
  17912.  {
  17913.      printf("%s", ptr);
  17914.      fflush(stdout);
  17915.  }
  17916.  
  17917.  extern far pascal dosgetinfoseg();
  17918.  
  17919.  ok_to_disp()
  17920.  {
  17921.  struct gdtinfoarea far *gdt;
  17922.  struct ldtinfoarea far *ldt;
  17923.  unsigned    gseg;
  17924.  unsigned    lseg;
  17925.  
  17926.      dosgetinfoseg((char far *)&gseg, (char far *)&lseg);
  17927.      gdt = (struct gdtinfoarea far *)((long)gseg << 16);
  17928.      ldt = (struct ldtinfoarea far *)((long)lseg << 16);
  17929.  
  17930.      return( gdt->foreground_process_id == ldt->parent_pid);
  17931.  }
  17932.  
  17933.  ───────────────────────────────────────────────────────────────────────────
  17934.  An overview of the way the example program, DLL_CHAT, interfaces to its
  17935.  dynamic-link library.
  17936.  ───────────────────────────────────────────────────────────────────────────
  17937.  
  17938.  ┌──────────────────────────────────────────────────────────────────────────┐
  17939.  │                  DLL_CHAT.C  (First session of DLL_CHAT)                 │
  17940.  ├─────────────────┬───────────────────┬──────────────────┬─────────────────┤
  17941.  │     login()     │   get_msg_cnt()   │    get_msg()     │   send_msg()    │
  17942.  └───┬────────────┴────┬─────────────┴─────┬───────────┴───┬────────────┘
  17943.      │       │        my_id      │         my_id    │       my_id      │
  17944.      │     my_id        │     msg_cnt        │      │         │     status
  17945.      │       │          │        │          ptr     │        ptr       │
  17946.      │       │          │        │           │      │         │        │
  17947.  ┌──────────┴─────┬────────────┴─────┬───────────┴─────┬───────────┴────┐
  17948.  │     login()     │   get_msg_cnt()   │    get_msg()     │   send_msg()    │
  17949.  ├─────────────────┼───────────────────┼──────────────────┼─────────────────┤
  17950.  │(returns first   │(returns msg cnt   │ (copies first    │(allocates memory│
  17951.  │unused login     │of outstanding     │ outstanding      │as required for  │
  17952.  │slot)            │messages for       │ msg for this     │message structure│
  17953.  │                 │login id)          │ id into array    │and message, then│
  17954.  │                 │                   │ at ptr)          │copies msg at ptr│
  17955.  │                 │                   │                  │into this memory)│
  17956.  ├─────────────────┴───────────────────┴──────────────────┴─────────────────┤
  17957.  │                DLL_CHAT.LIB  (Other sessions of DLL_CHAT)                │
  17958.  └──────────┬──────────────────┬──────────────────┬───────────────┬─────┘
  17959.      │       │          │        │           │       │         │      │
  17960.      │     my_id      my_id      │         my_id     │       my_id    │
  17961.      │       │          │     msg_cnt        │       │         │   status
  17962.      │       │          │        │          ptr      │        ptr     │
  17963.  ┌───┴────────────┬────┴─────────────┬─────┴───────────┬────┴───────────┐
  17964.  │     login()     │   get_msg_cnt()   │    get_msg()     │   send_msg()    │
  17965.  ├─────────────────┴───────────────────┴──────────────────┴─────────────────┤
  17966.  │                 DLL_CHAT.C  (Other sessions of DLL_CHAT)                 │
  17967.  └──────────────────────────────────────────────────────────────────────────┘
  17968.  
  17969.  
  17970.  Figure 8:
  17971.  
  17972.  DEF File for CHATLIB
  17973.  
  17974.  LIBRARY CHATLIB INITGLOBAL
  17975.  
  17976.  DATA SINGLE SHARED
  17977.  
  17978.  EXPORTS login
  17979.  EXPORTS get_msg_cnt
  17980.  EXPORTS send_msg
  17981.  EXPORTS logout
  17982.  EXPORTS get_msg
  17983.  
  17984.  Code listing for CHATLIB
  17985.  
  17986.  /**********************************************************************
  17987.  *    CHATLIB.C  - A demonstration Dynamic Link Library
  17988.  *
  17989.  *    (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
  17990.  *
  17991.  *    This DLL, when used with the CHAT program acts as a central
  17992.  *    repository for all messages being passed
  17993.  *
  17994.  *    Compile with:
  17995.  *
  17996.  *    cl /AL /Au /Gs /c chatlib.c
  17997.  *
  17998.  *    Note -    Though broken here the following two lines are
  17999.  *              entered as one line:
  18000.  *
  18001.  *    link startup+chatlib+chatmem,chatlib.dll,,llibcdll+doscalls,
  18002.  *    chatlib/NOE
  18003.  *
  18004.  *    Remember to move the DLL itself into your DLL library
  18005.  *    directory
  18006.  *
  18007.  ********************************************************************/
  18008.  
  18009.  #include    <stdio.h>
  18010.  #include    <stdlib.h>
  18011.  #include    <malloc.h>
  18012.  #include    <dos.h>
  18013.  
  18014.  #define      TRUE    1
  18015.  #define      FALSE    0
  18016.  #define      OK    TRUE
  18017.  #define      NOT_OK    FALSE
  18018.  
  18019.  #define      NULLP     ((char *)NULL)
  18020.  #define      GET_SEM    (dossemrequest(&semaphore, -1L))
  18021.  #define      RLS_SEM    (dossemclear(&semaphore))
  18022.  
  18023.  /* The following OS/2 system calls are made in this module: */
  18024.  
  18025.  extern far pascal dossemrequest();
  18026.  extern far pascal dossemclear();
  18027.  
  18028.  /* The following external calls are made in this module: */
  18029.  
  18030.  char *my_calloc();
  18031.  
  18032.  /* This semaphore used to coordinate access to "critical" areas */
  18033.  
  18034.  long    semaphore = 0;
  18035.  
  18036.  /*******************************************************************
  18037.  *    This structure defines the members of the linked list
  18038.  *    which starts at master_ptr.  Once a structure is allocated,
  18039.  *    it is never released, although the character array member
  18040.  *    msg_ptr points to will be released when the message is no
  18041.  *    longer needed
  18042.  ********************************************************************/
  18043.  #define    MSG    struct    _msg
  18044.  MSG{
  18045.      MSG       *next_ptr;  /* Point to next MSG, or NULLM       */
  18046.      char      *msg_ptr;   /* Point to the actual message       */
  18047.      int       msg_len;    /* length of the message - optional  */
  18048.      unsigned  f_word;     /* flag_word. When set to 0xfff      */
  18049.                            /* all chat members have seen this   */
  18050.                            /* message, so it can be freed       */
  18051.      };
  18052.  
  18053.  int    flag_word = 0xffff; /* This is the word that f_word is  */
  18054.                             /* set to initially. It is modified */
  18055.                             /* so that each bit is "off" if that*/
  18056.                             /* "user" is logged in              */
  18057.  
  18058.  #define    NULLM    ((MSG *)NULL)
  18059.  MSG    *master_ptr = NULLM;    /* Where the linked list begins    */
  18060.  
  18061.  /********************************************************************
  18062.  *    new_msg_struct(pointer to last MSG)
  18063.  *
  18064.  *    Allocates a new MSG, initializes the contents of the
  18065.  *    structure, and sets the linked list if not the first time
  18066.  *    called (last_msg != NULLM)
  18067.  *
  18068.  *    Returns a pointer to the structure allocated, NULLM if an error
  18069.  ********************************************************************/
  18070.  
  18071.  MSG    *new_msg_struct(MSG *last_msg)
  18072.  {
  18073.  MSG    *tmp_ptr;
  18074.  
  18075.      if  ((tmp_ptr = (MSG *)my_calloc(sizeof(MSG), 1)) == NULLM)
  18076.          return(NULLM);
  18077.  
  18078.      tmp_ptr->next_ptr = NULLM;
  18079.  
  18080.      tmp_ptr->msg_ptr = NULLP;
  18081.      tmp_ptr->msg_len = NULL;
  18082.  
  18083.      tmp_ptr->f_word = flag_word;
  18084.  
  18085.      if  (last_msg != NULLM)
  18086.          last_msg->next_ptr = tmp_ptr;
  18087.  
  18088.      return(tmp_ptr);
  18089.  }
  18090.  
  18091.  /********************************************************************
  18092.  *    initialize()
  18093.  *
  18094.  *    Called either by the initialization routine of the DLL, or by
  18095.  *    the first login. It allocates the first MSG structure, then
  18096.  *    allocates and sets up for the next member
  18097.  ********************************************************************/
  18098.  
  18099.  void far _loadds pascal initialize()
  18100.  {
  18101.  
  18102.      if  ((master_ptr = new_msg_struct(NULLP)) == NULLM)
  18103.      {
  18104.          printf("Couldn't allocate MSG memory for header...\n");
  18105.          exit(1);
  18106.      }
  18107.  
  18108.      new_msg_struct(master_ptr);
  18109.  }
  18110.  
  18111.  /********************************************************************
  18112.  *    login()
  18113.  *
  18114.  *    If the master MSG structure hasn't been allocated already by
  18115.  *    an earlier call to initialize() (by the DLL initialize
  18116.  *    routine), then make the call now.  Memory has already been
  18117.  *    allocated, therefore, so now give ourselves access to the
  18118.  *    segment we've allocated.
  18119.  *
  18120.  *    Get the next free bit slot in the flag word, set it to
  18121.  *    indicate it's in use, then return our login id.
  18122.  ********************************************************************/
  18123.  
  18124.  int far _loadds pascal login()
  18125.  {
  18126.  int    log_id;
  18127.  int    tmp_msk;
  18128.  
  18129.      if  (master_ptr == NULLM)
  18130.      {
  18131.          printf("Init in login\n");
  18132.          initialize();
  18133.      }
  18134.  
  18135.      my_getseg();
  18136.  
  18137.      GET_SEM;
  18138.      for (log_id= 0 ; log_id < 16 ; log_id++)
  18139.      {
  18140.          tmp_msk = mask(log_id);
  18141.          if  (flag_word & tmp_msk)
  18142.          {
  18143.              flag_word &= ~tmp_msk;
  18144.              RLS_SEM;
  18145.              return(log_id);
  18146.          }
  18147.  
  18148.      }
  18149.      RLS_SEM;
  18150.  
  18151.      printf("Login slots all used up!\n");
  18152.      exit(1);
  18153.  }
  18154.  
  18155.  /********************************************************************
  18156.  *    get_msg_cnt(login_id)
  18157.  *
  18158.  *    For every MSG structure in the linked list with an associated
  18159.  *    message attached to it, increment a counter if the id in
  18160.  *    question hasn't received it yet, then return that counter
  18161.  *    when we fall off the end.
  18162.  ********************************************************************/
  18163.  
  18164.  int far _loadds pascal get_msg_cnt(int id)
  18165.  {
  18166.  MSG    *tmp_ptr;
  18167.  int    tmp_cnt = 0;
  18168.  int    tmp_msk = mask(id);
  18169.  
  18170.      GET_SEM;
  18171.      for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
  18172.      {
  18173.          if  (!(tmp_ptr->f_word & tmp_msk))
  18174.              if    (tmp_ptr->msg_len)
  18175.                  tmp_cnt++;
  18176.      }
  18177.  
  18178.      RLS_SEM;
  18179.      return(tmp_cnt);
  18180.  }
  18181.  
  18182.  /********************************************************************
  18183.  *    send_msg(login_id, pointer_to_message)
  18184.  *
  18185.  *    If there are no other "chatter's" logged in, simply return.
  18186.  *    (Flag_word or'ed with our mask would be 0xfff)
  18187.  *
  18188.  *    Find a free MSG structure (guaranteed to have at least one,
  18189.  *    since every write leaves a free one allocated if its the last
  18190.  *    one in the linked list).
  18191.  *
  18192.  *    Allocate memory for the message, copy the message into it,
  18193.  *    then assign the pointer in the structure and the length of
  18194.  *    the message. Finally, allocate a new structure if required.
  18195.  ********************************************************************/
  18196.  
  18197.  int far _loadds pascal send_msg(int id, char far *ptr)
  18198.  {
  18199.  MSG    *tmp_ptr = master_ptr;
  18200.  int    tmp_len = strlen(ptr) + 1;
  18201.  
  18202.      if  ((flag_word | mask(id)) == 0xffff)
  18203.          return(OK);
  18204.  
  18205.      GET_SEM;
  18206.      while   (tmp_ptr->msg_len)
  18207.          tmp_ptr = tmp_ptr->next_ptr;
  18208.  
  18209.      if  ((tmp_ptr->msg_ptr = my_calloc(tmp_len, 1)) == NULLP)
  18210.      {
  18211.          printf("Can't allocate %d bytes for msg\n", tmp_len);
  18212.          RLS_SEM;
  18213.          return(NOT_OK);
  18214.      }
  18215.  
  18216.      strcpy(tmp_ptr->msg_ptr, ptr);
  18217.      tmp_ptr->msg_len = tmp_len;
  18218.      tmp_ptr->f_word = (flag_word | mask(id));
  18219.  
  18220.      if  (tmp_ptr->next_ptr == NULLM)
  18221.      {
  18222.          if  (new_msg_struct(tmp_ptr) == NULLM)
  18223.          {
  18224.              printf("Can't allocate new MSG_header\n");
  18225.              free_msg(tmp_ptr);
  18226.              RLS_SEM;
  18227.              return(NOT_OK);
  18228.          }
  18229.      }
  18230.  
  18231.      RLS_SEM;
  18232.      return(OK);
  18233.  }
  18234.  
  18235.  /********************************************************************
  18236.  *    logout(login_id)
  18237.  *
  18238.  *    Mark every mesage as read (freeing them if now "totally"
  18239.  *    read),reset the flag word, and then indicate that the logout
  18240.  *    worked.
  18241.  ********************************************************************/
  18242.  
  18243.  int far _loadds pascal logout(int id)
  18244.  {
  18245.  MSG    *tmp_ptr;
  18246.  int    tmp_msk = mask(id);
  18247.  
  18248.      GET_SEM;
  18249.      for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
  18250.          mark_msg(id, tmp_ptr);
  18251.  
  18252.      flag_word |= mask(id);
  18253.  
  18254.      RLS_SEM;
  18255.  
  18256.      printf("In logout ... Hit a Key:");fflush(stdout);
  18257.      getch();
  18258.      printf("\n\n\n\n");
  18259.  
  18260.      return(0);
  18261.  }
  18262.  
  18263.  /********************************************************************
  18264.  *    get_msg(login_id, pointer to buffer)
  18265.  *
  18266.  *    Find the first message the login_id hasn't read, then strcpy
  18267.  *    it into the buffer supplied. Then mark the message as read
  18268.  *    (freeing as required).
  18269.  ********************************************************************/
  18270.  
  18271.  int far _loadds pascal get_msg(int id, char far *ptr)
  18272.  {
  18273.  MSG    *tmp_ptr = master_ptr;
  18274.  int    tmp_msk = mask(id);
  18275.  
  18276.  
  18277.      GET_SEM;
  18278.      for(tmp_ptr = master_ptr; tmp_ptr; tmp_ptr = tmp_ptr->next_ptr)
  18279.      {
  18280.          if  (!(tmp_ptr->f_word & tmp_msk))
  18281.          {
  18282.              strcpy(ptr, tmp_ptr->msg_ptr);
  18283.              mark_msg(id, tmp_ptr);
  18284.              RLS_SEM;
  18285.              return(TRUE);
  18286.          }
  18287.      }
  18288.      RLS_SEM;
  18289.      return(FALSE);
  18290.  }
  18291.  
  18292.  /********************************************************************
  18293.  *    mark_msg(login id, pointer to message structure)
  18294.  *
  18295.  *    Mark our bit in the MSG f_word as set.  If then set to
  18296.  *    0xffff, the message is "totally" read, so free it.
  18297.  *
  18298.  *******************************************************************
  18299.  *
  18300.  *    free(pointer to message structure)
  18301.  *
  18302.  *    If there is a string associated with this structure, free the
  18303.  *    memory so used, then zero out the pointer and the msg_len
  18304.  ********************************************************************/
  18305.  
  18306.  mark_msg(int id, MSG *ptr)
  18307.  {
  18308.      ptr->f_word |= mask(id);
  18309.      if   (ptr->f_word == 0xffff)
  18310.          free_msg(ptr);
  18311.  }
  18312.  
  18313.  free_msg(MSG *ptr)
  18314.  {
  18315.    if (ptr->msg_ptr)
  18316.      my_free(ptr->msg_ptr);
  18317.  ptr->msg_ptr = NULLP;
  18318.  ptr->msg_len = NULL;
  18319.  }
  18320.  
  18321.              /* GENERAL ROUTINES */
  18322.  
  18323.  /* This routine merely returns with the bit corresponding
  18324.   * to our login set
  18325.   */
  18326.  
  18327.  mask(int log_id)
  18328.  {
  18329.      return(1 << (log_id - 1));
  18330.  }
  18331.  
  18332.  Additional Module for CHATLIB
  18333.  
  18334.  /*   CHATMEM.C - Memory allocation routines for shared DLL memory
  18335.  *
  18336.  *    (C) 1988, By Ross M. Greenberg for Microsoft Systems Journal
  18337.  *
  18338.  *    This module contains three functions. Allocation of memory,
  18339.  *    de-allocation of memory and the getseg call.
  18340.  *
  18341.  *    The current ANSI calloc/alloc/malloc sequence does not allow
  18342.  *    for an additional parameter to specify if memory requested
  18343.  *    through these functions is to be sharable or private.
  18344.  *    Therefore the MSC library calls all allocate private memory.
  18345.  *
  18346.  *    These routines allocate a 64K chunk of memory, requested from
  18347.  *    OS/2 as a sharable chunk, then dole it out using the DosSub
  18348.  *    allocation calls.
  18349.  *
  18350.  *    Only one 64K chunk is allocated: if more memory is desired an
  18351.  *    additional call to dosallocseg would have to be made and all
  18352.  *    sessions already logged in given access to the chunk. Out of
  18353.  *    laziness, that was not done for these demonstration
  18354.  *    routines.
  18355.  *
  18356.  *
  18357.  *    Compile with:
  18358.  *
  18359.  *    cl /AL /Au /Gs /c chatmem.c
  18360.  *
  18361.  *    Note - Though broken here, the following two lines are
  18362.  *           entered as one line:
  18363.  *
  18364.  *    link startup+chatlib+chatmem,chatlib.dll,,llibcdll+doscalls,
  18365.  *    chatlib/NOE
  18366.  *
  18367.  *    Remember to move the DLL itself into your DLL library
  18368.  *    directory
  18369.  ********************************************************************/
  18370.  
  18371.  #include    <stdio.h>
  18372.  #include    <stdlib.h>
  18373.  #include    <malloc.h>
  18374.  #include    <dos.h>
  18375.  
  18376.  #define    TRUE    1
  18377.  #define    FALSE   0
  18378.  
  18379.  /* The following OS/2 system calls are made in this module: */
  18380.  
  18381.  extern far pascal dosallocseg();
  18382.  extern far pascal dosfreeseg();
  18383.  extern far pascal dossuballoc();
  18384.  extern far pascal dossubset();
  18385.  extern far pascal dossubfree();
  18386.  extern far pascal dosgetseg();
  18387.  extern far pascal dossemrequest();
  18388.  extern far pascal dossemclear();
  18389.  
  18390.  #define    NULLP    (char *)NULL
  18391.  
  18392.  /*    This semaphore is so that we don't hurt ourselves as we
  18393.   *    allocate and deallocate memory
  18394.   */
  18395.  
  18396.  long    memory_semaphore = NULL;
  18397.  
  18398.  /*    This is the actual selector which the 64K dosallocseg()
  18399.   *    call returns
  18400.   */
  18401.  
  18402.  unsigned major_selector = NULL;
  18403.  
  18404.  /********************************************************************
  18405.  *    my_calloc(number_of_items,size_of_item)
  18406.  *
  18407.  *    Emulates the more typical calloc call, but returns memory
  18408.  *    which can later be allocated as sharable.
  18409.  *
  18410.  *    After the first call (which causes a 64K chunk to be
  18411.  *    allocated), all subsequent calls cause a dossuballoc call to
  18412.  *    be made, and a long pointer to the returned memory to be
  18413.  *    created and returned
  18414.  *
  18415.  *    The 64K chunk must be initialized by the dossubset call
  18416.  *    before it can be used by other dossub functions.
  18417.  *
  18418.  *    Because the dossubfree call requires a size, the size
  18419.  *    requested plus the sizeof an int is actually allocated and
  18420.  *    the size of the total request is then stored in the first two
  18421.  *    bytes of the returned character array. The ptr returned,
  18422.  *    however, is this memory location plus the initial sizeof and
  18423.  *    int-therefore the bookkeeping is transparent to the
  18424.  *    application task.
  18425.  ********************************************************************/
  18426.  
  18427.  char *
  18428.  my_calloc(size1, size2)
  18429.  int    size1;
  18430.  int    size2;
  18431.  {
  18432.  unsigned    long    selector;
  18433.  int    stat;
  18434.  char   *ptr;
  18435.  int    sizeit = (size1 * size2) + sizeof(int);
  18436.  
  18437.      dossemrequest(&memory_semaphore, -1L);
  18438.      if  (!major_selector)
  18439.      {
  18440.         if   (stat = dosallocseg(0, &major_selector, 3))
  18441.          {
  18442.              printf("dosalloc error:%d\n", stat);
  18443.              dossemclear(&memory_semaphore);
  18444.              return(NULLP);
  18445.          }
  18446.  
  18447.         if   (stat = dossubset(major_selector, 1, 0))
  18448.          {
  18449.              printf("Error in dossubset:%d\n", stat);
  18450.              dossemclear(&memory_semaphore);
  18451.              return(NULLP);
  18452.          }
  18453.      }
  18454.  
  18455.      selector = 0;
  18456.      if  (stat = dossuballoc(major_selector, &selector, sizeit))
  18457.      {
  18458.          printf("dossuballoc error:%d\n", stat);
  18459.          dossemclear(&memory_semaphore);
  18460.          return(NULLP);
  18461.      }
  18462.  
  18463.      dossemclear(&memory_semaphore);
  18464.      ptr = (char *)(((long)major_selector << 16) + (long)selector);
  18465.      memset(ptr, (char)NULL, sizeit);
  18466.      *(int *)ptr = sizeit;
  18467.      return(ptr + sizeof(int));
  18468.  }
  18469.  
  18470.  /********************************************************************
  18471.  *    my_free(pointer_to_a_character_array_previously_my_calloc'ed)
  18472.  *
  18473.  *    Subtract sizeof an int from the pointer, dereference as an
  18474.  *    int, then free that number of bytes.
  18475.  *
  18476.  ********************************************************************/
  18477.  
  18478.  my_free(ptr)
  18479.  char    *ptr;
  18480.  {
  18481.  int    stat;
  18482.  
  18483.      ptr -= sizeof(int);
  18484.  
  18485.      dossemrequest(&memory_semaphore, -1L);
  18486.      if (stat = dossubfree(major_selector, FP_OFF(ptr), *(int *)ptr))
  18487.      {
  18488.          printf("Error freeing: %lx\n", ptr);
  18489.          exit(1);
  18490.      }
  18491.      dossemclear(&memory_semaphore);
  18492.  }
  18493.  
  18494.  /********************************************************************
  18495.  *    my_getseg()
  18496.  *
  18497.  *    Causes the memory affilaited with the major_selector to
  18498.  *    become accessible to this process.
  18499.  ********************************************************************/
  18500.  
  18501.  my_getseg()
  18502.  {
  18503.  int    stat;
  18504.  
  18505.  if  (stat=dosgetseg(major_selector))
  18506.          printf("Error on getseg:%d\n", stat);
  18507.      return(stat);
  18508.  }
  18509.  
  18510.  }
  18511.  
  18512.  
  18513.  Figure 9:  A DLL Initialization Routine
  18514.  
  18515.  EXTRN     INITROUTINE:FAR
  18516.            ASSUME    CS:_TEXT
  18517.  _TEXT     SEGMENT BYTE PUBLIC 'CODE'
  18518.  START     PROC FAR
  18519.            call INITROUTINE    ; the real initialization routine
  18520.            ret
  18521.  START     ENDP
  18522.  _TEXT     ENDS
  18523.  END       START               ; defines auto-init entry point
  18524.  
  18525.  
  18526.  Figure 10:  An Example of Semaphored Printf() vs. "Free-style" Printf()
  18527.  
  18528.  Consider the two code fragments below. In the semaphore version of the
  18529.  code, individual printf()'s are not interrupted──whatever they are printing
  18530.  will be printed to completion with other calls to the same printf()
  18531.  routine waiting on a semaphore before proceeding. This semaphore is reset
  18532.  when the current process using the library function is exiting from the
  18533.  library function.
  18534.  
  18535.  So-called "free-style" functions, on the other hand, can be interrupted
  18536.  at any time-even in the middle of printing. Therefore, you might have two
  18537.  (or more!) streams of data merged into some unreadable mess.
  18538.  
  18539.  It's worse, however, if global data is in use by the interrupted free-
  18540.  style function. Such data can be modified by the "interrupter," and could
  18541.  cause unpredictable results.
  18542.  
  18543.   Program 1                           Program 2
  18544.  
  18545.   main()                              main()
  18546.   {                                   {
  18547.     printf("Task1:%d\n", cnt++);        printf("Task2:%d\n", cnt++);
  18548.   }                                   }
  18549.  
  18550.              Semaphored                          Free Style
  18551.  
  18552.              Task1:1                             Task2:1
  18553.              Task2:1                             Task1:1
  18554.              Task1:2                             Task1:2
  18555.              Task1:3                             Task1:3
  18556.              Task1:4                             Task2:2
  18557.              Task2:2                             Task2:3
  18558.              Task2:3                             Task2:4
  18559.              Task1:4                             Task1:4
  18560.                 ∙                                   ∙
  18561.                 ∙                                   ∙
  18562.                 ∙                                   ∙
  18563.  What would the apparent results be if two sessions, each normally
  18564.  independent of the other, but sharing a Dynamic-Link Library version of
  18565.  printf(), were to try to execute the printf() routine in a semaphored
  18566.  versus a free-style approach? The two program fragments above, each
  18567.  running as a separate session, show the differences. In the semaphore
  18568.  version, each call made to printf() by each session is run to completion,
  18569.  although the "set" might be interrupted. In the free-style version, there
  18570.  is not even a guarantee that any given printf() will finish before it is
  18571.  interrupted. The same would also hold true for multithreads of the same
  18572.  session using a common printf() routine. The motto? "If in doubt, use
  18573.  semaphores."
  18574.  ████████████████████████████████████████████████████████████████████████████
  18575.  
  18576.  New Compiler Technology Boosts Microsoft QuickBASIC 4.0 Productivity
  18577.  
  18578.  Augie Hansen
  18579.  
  18580.  Compiler technology takes a quantum leap forward with the most recent
  18581.  version of Microsoft(R) QuickBASIC. If you already have Microsoft QuickBASIC
  18582.  Version 3.0, you received an invitation to upgrade to Version 4.0 for a
  18583.  nominal charge, but don't let the word "upgrade" fool you. The Microsoft
  18584.  QuickBASIC 4.0 compiler is a brand new product. The compiler, as in
  18585.  earlier versions, comes in two flavors: a traditional command-line
  18586.  oriented compiler, BC, and a speedy in-memory integrated environment, QB,
  18587.  that truly deserves the label "instant environment."
  18588.  
  18589.  Microsoft QuickBASIC 4.0 is based on threaded pseudocode technology that
  18590.  has been under development at Microsoft for a number of years. The p-code
  18591.  technology, as it is called, draws upon the threaded-code interpreter
  18592.  environments used by FORTH programming products for many years, but with
  18593.  some important extensions and improvements.
  18594.  
  18595.  This article will take a close look at threaded p-code and the way it is
  18596.  applied to Microsoft QuickBASIC 4.0. We'll show you how the tightly
  18597.  coupled editor, p-code interpreter, and debugger all work together to
  18598.  provide instant BASIC programming that will boost your productivity.
  18599.  The development of a video terminal emulation program provides an
  18600.  example of the program in action.
  18601.  
  18602.  
  18603.  Features/Enhancements
  18604.  
  18605.  The p-code technology used in Microsoft QuickBASIC 4.0 offers a
  18606.  programming environment with startling advantages over earlier
  18607.  integrated environments. When you type a program line and press Enter
  18608.  or the down arrow key, the line is immediately compiled to intermediate
  18609.  p-code. At the same time, the line is formatted by the editor so that all
  18610.  BASIC keywords are displayed in uppercase, and all operators are
  18611.  surrounded by spaces.
  18612.  
  18613.  The line is also checked in real time for syntax errors. If any are found,
  18614.  they are reported in a dialog box; do not ignore such errors. The system
  18615.  lets you move off the line after it has told you about the problem, but be
  18616.  sure the line is syntactically correct before moving on to some other task.
  18617.  
  18618.  The net result is that a program is always ready to run virtually
  18619.  instantly. You can select Start in the Run menu (or press Shift-F5) at any
  18620.  time to see the results of your programming labors. The first time you
  18621.  start a program, it is translated from editable intermediate p-code to
  18622.  executable threaded p-code, which is as close to unoptimized native
  18623.  machine code as you can get without actually being there. This is as far
  18624.  as the code can be taken so that it can still be returned to the ASCII
  18625.  representation for editing.
  18626.  
  18627.  You can stop program execution at any time, change the values of
  18628.  variables, add or delete any number of lines, and then return to running
  18629.  the program in the blink of an eye. The trick is that Microsoft
  18630.  QuickBASIC 4.0 works on one small unit of code at a time; it decompiles
  18631.  only what you are going to be working on. If, for example, you are
  18632.  working on changes to a SUB (subprogram), only that SUB is returned to
  18633.  an editable intermediate p-code state. When you finish your changes, you
  18634.  simply select Run-Continue (or press F5) to continue executing where you
  18635.  left off before editing. Since all variable values are preserved, your
  18636.  program resumes execution in exactly the same state it was in before you
  18637.  interrupted it.
  18638.  
  18639.  Built-in debugging, as well as the syntax checking already mentioned, is
  18640.  patterned after Microsoft CodeView(R). Within the Microsoft QuickBASIC 4.0
  18641.  instant environment, you can set watch expressions, watchpoints, and
  18642.  breakpoints, single-step program execution, and even backtrack through
  18643.  previously executed statements.
  18644.  
  18645.  The separate command-line version of the Microsoft QuickBASIC 4.0
  18646.  compiler, the BC.EXE program, provides three important benefits. To begin
  18647.  with, it lets you produce disk-based executable programs in either
  18648.  standalone form or smaller executable versions that work with a single
  18649.  run-time module. Second, it provides a debugging code option that lets you
  18650.  use CodeView for source level debugging of your programs with the best
  18651.  available full-screen debugger. Finally, it makes mixed-language
  18652.  programming possible, allowing you to use existing routines written in
  18653.  Microsoft C (Version 5.0 and QuickC(TM)), Pascal, FORTRAN, and the Macro
  18654.  Assembler (MASM).
  18655.  
  18656.  Recursion has been added to this version of Microsoft QuickBASIC. Although
  18657.  recursion offers no substantial code size or speed advantages over
  18658.  equivalent iterative programming methods, some types of problems, such
  18659.  as the Quick Sort procedure and DOS-directory traversal, lend themselves
  18660.  conceptually to recursive solutions.
  18661.  
  18662.  Another valuable addition to BASIC is the user-definable data type, which
  18663.  lets you define structured data types that are equivalent to C structures
  18664.  and Pascal records. Records are important in the creation of composite
  18665.  data types which simplify organizing and tracking data and ease the
  18666.  burden of dealing with random-access files.
  18667.  
  18668.  Microsoft QuickBASIC 4.0 supports only IEEE number formats. The Microsoft
  18669.  binary format used in earlier versions of the compiler has been dropped,
  18670.  but functions have been added in order to permit data stored in the
  18671.  Microsoft binary format to be translated to the IEEE format. Also,
  18672.  automatic detection and use of a numeric data processor (NDP), a math
  18673.  coprocessor, has been added. Executable programs can run on any IBM-
  18674.  compatible machine regardless of whether it has an NDP. If one is present,
  18675.  it is used to obtain the greatest execution speed; if not, math operations
  18676.  are emulated in software. There is no longer any need to have two
  18677.  separate versions of programs for different hardware configurations.
  18678.  
  18679.  The help system now has three levels. One level is visible on the status
  18680.  line at the bottom of the screen whenever a menu or command selection is
  18681.  visible at the top. You can also get a series of general help panels by
  18682.  pressing F1 or pressing Alt-H and choosing the General... option.
  18683.  
  18684.  Best of all, a new context-sensitive help feature gives you on-line access
  18685.  to BASIC manual pages for all statements, functions, and other keywords.
  18686.  You simply move the editing cursor into the item of interest, and press
  18687.  Shift-F1 or select the Topic option from the Help menu. The view window
  18688.  shrinks as much as necessary to display the help frame above your source
  18689.  code (see Figure 1). The editor is still active while the help
  18690.  information is on the screen, letting you continue to edit while looking
  18691.  at the syntax of a statement. After you have viewed the help window, press
  18692.  Esc to close it.
  18693.  
  18694.  For those of you who are beginning to program under an OS/2 system,
  18695.  Microsoft QuickBASIC 4.0 will be provided as a real-mode option under the
  18696.  BASIC compiler Version 6.0. The BASIC compiler 6.0, which is completely
  18697.  compatible with Microsoft QuickBASIC 4.0, is essentially what Microsoft
  18698.  will provide for OS/2 BASIC programming. All OS/2 system calls will be
  18699.  supported directly from BASIC in the OS/2 version of the compiler.
  18700.  
  18701.  
  18702.  Program Development
  18703.  
  18704.  The Microsoft QuickBASIC development cycle is shown in Figure 2. The
  18705.  figure compares the Version 3.0 development cycle with the Version 4.0
  18706.  cycle. As you can see, the compile step appears to be missing from the 4.0
  18707.  cycle, at least from your perspective as a developer.
  18708.  
  18709.  The compile step really is there, of course, but it happens so fast that
  18710.  it's not usually visible to you. When you first read in a new module's
  18711.  source from disk, you only see a compile delay as the statements are
  18712.  parsed and compiled, and even that process is fast. Creating a new program
  18713.  or editing a program that is already in memory produces an interpreter-
  18714.  like operation.
  18715.  
  18716.  Transitions among the three steps are bidirectional. You can switch from
  18717.  the "run" mode to the "edit" mode and back easily and quickly, or between
  18718.  run and "debug", or edit and debug. The seamless integration of the edit,
  18719.  run, and debug steps into an instant environment is something to behold.
  18720.  
  18721.  Microsoft QuickBASIC 4.0 is heavily geared toward modular programming. The
  18722.  recommended units of modularity are procedures called subprograms (SUBs)
  18723.  and functions (FUNCTIONs). FUNCTIONs are new to the environment and
  18724.  should not be confused with the DEF FNs of older BASICs. Subprograms
  18725.  have many of the attributes of BASIC subroutines, but you don't have to
  18726.  GOSUB much anymore. Indeed, there are many reasons favoring the use of
  18727.  subroutines instead of traditional BASIC subprograms.
  18728.  
  18729.  SUBs accept parameters and provide for local storage. A variable defined
  18730.  inside a subprogram is not visible outside that subprogram unless you go
  18731.  out of your way to make it visible by sharing it. You can pass values into
  18732.  a SUB via parameters that use the call-by-reference method, which makes
  18733.  the address of a parameter available to the SUB so that the statements
  18734.  in the SUB can affect the parameter itself.
  18735.  
  18736.  If you want call-by-value parameter passing, which means that a SUB can
  18737.  use the value passed as a parameter but cannot access the original
  18738.  storage location, you can simulate it by assigning parameters into
  18739.  intermediate variables and passing the intermediates. You can also
  18740.  surround the expression or variable identifier in parentheses to force
  18741.  call-by-value parameter passing.
  18742.  
  18743.  Functions are similar to subprograms, except that they return a value by
  18744.  assigning the value to the function name. You can use functions in expres-
  18745.  sions just as you use BASIC intrinsic functions, such as the INPUT$
  18746.  function.
  18747.  
  18748.  You can declare variables local to either subprograms or functions as
  18749.  STATIC so that they retain their values from one call to the next.
  18750.  Alternatively, you can declare them without the STATIC keyword, in which
  18751.  case all local variables are dynamic. They are managed on the stack and
  18752.  exist only while the procedure is executing.
  18753.  
  18754.  When a program is in memory, it typically has code at two levels: module
  18755.  level and procedure level. Module-level code is at the top level. Items
  18756.  declared and defined at module level are global and are available
  18757.  throughout the module. Procedure-level code is contained within
  18758.  procedures and has local scope by default. Statements within procedures
  18759.  can access module-level items if those items are declared in DIM or COMMON
  18760.  statements. Procedures can also declare SHARED access to module-level
  18761.  items.
  18762.  
  18763.  Quick Libraries are another important tool for writing modular programs.
  18764.  You can create procedures in any Microsoft language, compile them to OBJ
  18765.  files, and collect them into Quick Libraries (QLB extension) that can
  18766.  then be loaded into the Microsoft QuickBASIC environment. The procedures
  18767.  in loaded Quick Libraries are immediately available for use in the active
  18768.  program. You can also ship libraries to other Microsoft QuickBASIC 4.0
  18769.  users to incorporate into their programs.
  18770.  
  18771.  You can create Quick Libraries and standard standalone libraries (LIB
  18772.  extension) either from the Microsoft QuickBASIC instant environment or
  18773.  from the command line. However, the one-step process of the instant
  18774.  environment makes creating libraries straightforward and simple.
  18775.  
  18776.  
  18777.  Threaded P-Code
  18778.  
  18779.  As we have already seen, Microsoft QuickBASIC 4.0 is both a compiler and
  18780.  an interpreter. The compiler keeps your program in a pseudocode state
  18781.  that is about 90 percent machine code. This p-code retains enough
  18782.  information to allow reconstruction of your original source while having
  18783.  only one internal representation of the program in memory at a time.
  18784.  
  18785.  This is crucial because the program is always ready to be run, debugged,
  18786.  and edited without a significant amount of translation time, and almost
  18787.  no execution speed penalty. And because of the single representation of
  18788.  your program instead of separate source, object, and executable
  18789.  representations in memory, there is more room for your program.
  18790.  
  18791.  The threaded p-code interpreter is not a discrete program entity, but
  18792.  rather a distributed interpreter. When you key in a BASIC source line,
  18793.  Microsoft QuickBASIC immediately checks its syntax. If errors are found,
  18794.  you are obliged to remove them before proceeding. If the syntax is good,
  18795.  the source line is translated into a threaded p-code representation that
  18796.  consists of a series of executor addresses, each of which is simply the
  18797.  memory address of an executor.
  18798.  
  18799.  An executor is a routine that participates in doing the work of a BASIC
  18800.  statement. A single BASIC statement usually results in a series of
  18801.  executors being chained together, or threaded. The threading is achieved
  18802.  by the distributed interpreter, which consists of two machine
  18803.  instructions. The cost is four bytes and seven CPU cycles per executor.
  18804.  Each executor looks like this:
  18805.  
  18806.    Executor routine + LODSW ES + JMP AX
  18807.  
  18808.  That's it. Your program is executed simply by chaining together all the
  18809.  executors that are needed to carry out its tasks. There is no wasteful
  18810.  CALL/RET instruction overhead, so your program runs quickly-about as fast
  18811.  as unoptimized machine code will run.
  18812.  
  18813.  Figure 3 shows how Microsoft QuickBASIC handles your program. The
  18814.  environment has three states called "parsed," "symbolic," and "threaded."
  18815.  A threaded p-code system with only the parsed and threaded states would
  18816.  appear slow because the time needed to translate from parsed code to
  18817.  threaded p-code accumulates. However, by introducing the intermediate
  18818.  symbolic state, Microsoft has achieved the goal of instant program
  18819.  changes.
  18820.  
  18821.  The parsed state is the result of parsing your BASIC statements, either
  18822.  from keyboard input or from disk files. Most of the syntax checking is
  18823.  done during this translation. Microsoft QuickBASIC 4.0 has the portions
  18824.  of your program that are affected by editing changes to COMMON, SHARED, or
  18825.  DEF statements in this state while the changes are made. Translation back
  18826.  to the threaded state occurs at a rate of about 60,000 lines per minute
  18827.  on an 8-MHz IBM(R) PC/AT(R).
  18828.  
  18829.  The symbolic state is distinguished by the efficient symbol table
  18830.  information that permits fast data access. Most editing that does not
  18831.  involve global impacts is done in this state. Only the portions of your
  18832.  program that are affected by editing need to be brought to this state.
  18833.  
  18834.  Your program is run and debugged in the threaded state; it is in the
  18835.  threaded state most of the time. Translation from symbolic to threaded
  18836.  state involves inserting type checking code, binding procedure calls,
  18837.  control structures, labels to memory addresses, linking COMMON data, and
  18838.  generating the p-code executor addresses.
  18839.  
  18840.  When you make nonglobal changes to your program, only the affected
  18841.  portions are returned to the symbolic state. Before the program can run
  18842.  again, the edited portions are translated back to threaded state. The
  18843.  round trip from threaded to symbolic and back to threaded occurs at about
  18844.  150,000 lines per minute on a typical 8MHz AT machine. Because you usually
  18845.  deal with a single program item at a time, such as a FUNCTION or SUB, this
  18846.  translation is virtually instantaneous.
  18847.  
  18848.  
  18849.  Debugging Made Easy
  18850.  
  18851.  The tight coupling between the run step and the edit step (see Figure 1)
  18852.  is also true of the relationship of the run and debug steps. The built-
  18853.  in debugging feature is a substantial subset of the Microsoft CodeView
  18854.  full-screen symbolic debugger. It lets you observe any variable or set of
  18855.  variables in your program, single-step execution, set breakpoints, trace
  18856.  the stack, and get a statement execution history. If you detect a run-
  18857.  time error condition, you can use the editor to make needed changes
  18858.  quickly and return immediately to the run step. SET NEXT STATEMENT is
  18859.  also available, and allows the programmer to set the next statement to be
  18860.  executed.
  18861.  
  18862.  You can set a watchpoint, which is an expression that causes program
  18863.  execution to stop when it becomes TRUE. The debugger opens a "watch
  18864.  window" just above the view window to your source code and displays all
  18865.  watch information in it. To observe the value of a variable or an
  18866.  expression as you run your program, you can set watch expressions and
  18867.  breakpoints. The debugger displays the values of watch expressions in
  18868.  the watch window, stopping at each breakpoint so you can see the values
  18869.  before continuing execution.
  18870.  
  18871.  You can also single-step program execution (F8) to track values as each
  18872.  statement is executed. In this mode, all statements in SUBs and
  18873.  FUNCTIONs are single-stepped. You can also do procedure stepping (F10),
  18874.  which treats each SUB and FUNCTION as if it were a single statement. In
  18875.  addition, you can turn on Trace mode, which will do a slow-motion single-
  18876.  step through your program, making flow analysis and diagnosis a breeze.
  18877.  
  18878.  A history feature, enabled by either Trace On or a separate History On
  18879.  activation, allows you to record up to the last 20 lines executed in your
  18880.  program. You can then scroll backward and forward through the lines to
  18881.  examine program execution. This is particularly useful when you need to
  18882.  follow branching patterns of IF and SELECT CASE statements and to observe
  18883.  looping statements.
  18884.  
  18885.  If the built-in debugging features aren't enough for your needs, you can
  18886.  also create EXE files with debugging code that let you use CodeView for
  18887.  greater access to the most intimate details of your program. CodeView is
  18888.  not supplied with Microsoft QuickBASIC, but it comes with MASM and other
  18889.  Microsoft language products. To debug pure BASIC code, the built-in
  18890.  debugger is probably more powerful than CodeView. However, CodeView
  18891.  compatibility is invaluable for mixed-language programming.
  18892.  
  18893.  
  18894.  Smart Editor
  18895.  
  18896.  The editing capabilities of Microsoft QuickBASIC 4.0 are quite impressive.
  18897.  Syntax-directed editing, "pretty printing" of source code lines, and
  18898.  WordStar(R) editing-command compatibility are among the editor's many
  18899.  features.
  18900.  
  18901.  Microsoft QuickBASIC breaks a BASIC program module into editable and
  18902.  compilable units, such as SUBs and FUNCTIONs. As you create or edit, the
  18903.  editor keeps track of these units and displays information about them on
  18904.  demand in a program item display window. You select the program item you
  18905.  want to edit by moving a highlight and choosing one of several
  18906.  commands.
  18907.  
  18908.  The editor supports split-screen editing so that you can edit one item
  18909.  while viewing another. It also allows you to switch to an immediate window
  18910.  to execute BASIC statements in what is the equivalent of direct mode in
  18911.  earlier versions of the BASIC interpreter. The split screen capability
  18912.  also works when debugging, which effectively allows you to view both the
  18913.  calling code and the called code simultaneously.
  18914.  
  18915.  You can use the editor as a standard text editor for ASCII files if you
  18916.  wish. You simply turn off the BASIC Syntax Checking feature in the Edit
  18917.  menu in order to disable the sensitivity to BASIC language statement
  18918.  syntax.
  18919.  
  18920.  
  18921.  Command-line Compiler
  18922.  
  18923.  The Microsoft QuickBASIC instant environment is great when learning to
  18924.  program in BASIC, experimenting, and doing modest program development
  18925.  tasks. Should you get involved in large program development projects,
  18926.  perhaps working with other programmers, or if you need to do mixed-
  18927.  language programming using C, Pascal, FORTRAN, or MASM in addition to
  18928.  BASIC, you will probably need to use the command-line compiler.
  18929.  
  18930.  The BC program, in concert with MAKE, LINK, and LIB, provides the
  18931.  traditional compiler you need to handle large projects. Figure 4 shows
  18932.  how the programs work together to produce executable programs and libraries.
  18933.  Note that you can even use the QB instant environment as a front-end
  18934.  development tool for the BC compiler. When you ask QB to produce an EXE
  18935.  file, it calls on BC and the linker, LINK, to translate your program source
  18936.  into optimized machine instructions.
  18937.  
  18938.  Mixed-language programming involves the use of Microsoft-format object
  18939.  libraries that can be linked into an executable program. Both the QB and
  18940.  BC programs let you create object library (LIB) files. The LINK program
  18941.  supplied with Microsoft QuickBASIC can link object modules produced by
  18942.  the C, Pascal, FORTRAN, and BASIC compilers, as well as by MASM.
  18943.  
  18944.  Figure 5 shows the way an executable program might be created by
  18945.  combining Microsoft QuickBASIC 4.0, QuickC or C 5.0, and MASM 5.0 object
  18946.  modules. The latest CodeView debugger can debug such a mixed-language
  18947.  program at the appropriate source level as long as you specify that
  18948.  debugging code be included at each compilation and assembly step. You
  18949.  can also create Quick Libraries to be used by Microsoft QuickBASIC.
  18950.  
  18951.  
  18952.  User-defined Types
  18953.  
  18954.  Microsoft QuickBASIC 4.0 adds user-defined types to its growing list of
  18955.  modern programming features. This is something that BASIC has needed for
  18956.  a long time. A user-defined type is a data type that contains more than
  18957.  one element. C has structures and unions, and Pascal has records; now
  18958.  Microsoft QuickBASIC has TYPEs.
  18959.  
  18960.  User-defined types let you model program data to fit real-world
  18961.  conditions. Instead of having lots of separate variables that appear to
  18962.  have no relationship, you can now define data types that clearly show
  18963.  data relationships. Each element in a defined type must have a type name
  18964.  of INTEGER, LONG, SINGLE, DOUBLE, STRING (with a length specifier), or
  18965.  another user-defined type.
  18966.  
  18967.  For example, you can easily mirror the structure of an inventory-control
  18968.  card. Use the keyword TYPE to define the structure of the data, as shown
  18969.  in Figure 6. Having defined the type, you can declare variables of this
  18970.  type by using the DIM, REDIM, COMMON, STATIC, and SHARED statements.
  18971.  
  18972.  To reserve storage for a set of 100 cards, you declare an array of items
  18973.  of this type:
  18974.  
  18975.    DIM CardTable(100) AS_ CardType
  18976.  
  18977.  You must define all user types at the module level. However, you can
  18978.  declare variables of the defined type at both the module level and the
  18979.  procedure level. Within a defined type, each string must have a fixed
  18980.  length, which is specified on a string-by-string basis by the asterisk
  18981.  number modifier. Thus, PartNumber AS STRING * 20 allocates a 20-character
  18982.  string to the part number. In programs, you can use the new functions
  18983.  LTRIM$ and RTRIM$ to remove leading and trailing blanks, respectively, in
  18984.  fixed-length strings.
  18985.  
  18986.  The Microsoft QuickBASIC documentation favors the use of the term "record"
  18987.  to describe a variable of user-defined type. To access a record element,
  18988.  use the period operator. The following statement assigns a value of 20 to
  18989.  the StockQty element of the tenth card in CardTable:
  18990.  
  18991.    CardTable(10).StockQty_  = 20
  18992.  
  18993.  To make things even more interesting, you can use defined data types in
  18994.  the definition of elements of yet other user-defined types. The Supplier
  18995.  AS SupplyType element, for example, indicates that a variable of the
  18996.  user-defined type SupplyType is part of the user-defined CardType
  18997.  definition.
  18998.  
  18999.  
  19000.  A Video Terminal Emulator
  19001.  
  19002.  The best way to see how Microsoft QuickBASIC 4.0 works is to try it. The
  19003.  program shown in Figure 7 and called ADM3A.BAS is a simple video terminal
  19004.  emulator. It is a vehicle for showing off several of the new features of
  19005.  Microsoft QuickBASIC and demonstrating the user interface and is a useful
  19006.  program in its own right. ADM3A.BAS makes an IBM-compatible PC act like a
  19007.  simple video terminal, the Lear Siegler adm3a.
  19008.  
  19009.  I wrote this terminal emulation because the adm3a terminal is recognized
  19010.  by most UNIX(R) and XENIX(R) systems as well as many other multiuser systems.
  19011.  The emulator allows me access to several visually oriented programs, from
  19012.  the VI editor to custom-designed database programs.
  19013.  
  19014.  Figure 8 shows the Microsoft QuickBASIC Run menu. To run the emulator from
  19015.  the Microsoft QuickBASIC environment, you would execute the Start command
  19016.  by highlighting it on the menu or by simply pressing the keyboard shortcut
  19017.  command, Shift-F5, while in the editing mode. Figure 8 shows the
  19018.  selection of the Make EXE File... command, which produces an executable
  19019.  program that you can run at the DOS command level. All Microsoft
  19020.  QuickBASIC commands are available in menus like this, and all frequently
  19021.  used commands have shortcut keyboard aliases.
  19022.  
  19023.  The ADM3A program consists of a single module with a half-dozen
  19024.  subprograms. The module-level code declares SUBs to let the compiler check
  19025.  both the number of parameters to be passed to each SUB and their types.
  19026.  Figure 9 shows the program item list that is maintained by Microsoft
  19027.  QuickBASIC 4.0. The list keeps track of modules and procedures within
  19028.  modules. You use this work screen to select, move, and delete modules and
  19029.  procedures. The smart editor normally allows you to view and edit one
  19030.  program item at a time. However, you can split the window into two parts
  19031.  with the View menu to see different parts of a program at the same time.
  19032.  
  19033.  ts
  19034.  The module-level code defines a set of symbolic constants for logical
  19035.  values, colors, and cursor control. When you read a BASIC program, seeing
  19036.  a cold, hard number like 3 gives you virtually no information about what
  19037.  the value means. The context may provide some help, but
  19038.  
  19039.    COLOR 3, 1
  19040.  
  19041.  is still a bit mysterious if you don't know what the numbers 3 and 1 stand
  19042.  for.
  19043.  
  19044.  Using symbolic names for constant values is a better practice. Thus, the
  19045.  definition
  19046.  
  19047.    CONST BLACK = 0, _BLUE = 1, ..., CYAN = 3
  19048.  
  19049.  lets you write a statement like
  19050.  
  19051.    COLOR CYAN, BLUE
  19052.  
  19053.  that provides the information a reader needs at a glance.
  19054.  
  19055.  Notice the definition of a "record" data type, WinType. The "window type"
  19056.  relates variables for the top, bottom, left, and right of a screen
  19057.  window. The variables CmdWin and ViewWin are declared to be WinType
  19058.  record type by the dimension statements.
  19059.  
  19060.  The assignment statements following the variable declarations set up two
  19061.  window areas on the ADM3A program screen. The top screen row becomes the
  19062.  command window, and the rest of the screen is the view window. This
  19063.  arrangement works well because the physical screen of an adm3a terminal
  19064.  has 24 screen rows and is 80 columns wide. Putting the command line at
  19065.  the top of the screen keeps it from being a distraction as the user's eyes
  19066.  move from screen to keyboard and back. The InitScreen procedure takes care
  19067.  of partitioning the screen into the command window and the view window
  19068.  portions. The VIEW PRINT statement causes scrolling to occur within the
  19069.  view window when print statements attempt to write past the last line.
  19070.  The command window is stationary in this program.
  19071.  
  19072.  Communications setup is easy in BASIC. The OPEN COM statement takes a
  19073.  string parameter that specifies the serial port (COM1: or COM2:), the
  19074.  transmission speed, and settings for parity and number of data bits and
  19075.  stop bits. It opens the communication channel, if the necessary hardware
  19076.  is installed, and sets up transmit and receive buffers and an interface
  19077.  to your program.
  19078.  
  19079.  The ADM3A program uses an optional DOS environment variable to customize
  19080.  its setup. If the variable COMPARMS is defined in the DOS environment,
  19081.  its value is used as the OPEN COM string argument. Otherwise, ADM3A uses a
  19082.  set of built-in values. It then sets the values of the communication port
  19083.  address and the BREAK signal mask, which are used in the BreakSignal
  19084.  procedure.
  19085.  
  19086.  The main program loop alternately monitors the keyboard and the incoming
  19087.  communication line buffer for input. From the perspective of the local
  19088.  system, the program takes input from the keyboard and sends it to the
  19089.  remote system over the communication line. It takes incoming data from the
  19090.  communication line and puts it on the screen. This basic pattern is
  19091.  altered by certain special codes.
  19092.  
  19093.  When the user types anything at the keyboard, it is sent to the remote
  19094.  system unless it is an extended code. The three ADM3A commands, Break,
  19095.  Dial, and Quit, are initiated by extended codes, which are two-byte
  19096.  sequences that have a NULL character as the first byte.
  19097.  
  19098.  When INKEY$ returns an extended code, the value is sent in the form of an
  19099.  argument to the DoCommand subprogram, which examines the second byte, the
  19100.  scan code, and compares it with a list of command codes. The Alt-b and
  19101.  Alt-d key combinations cause other procedures to be invoked. The Alt-q
  19102.  command quits the emulator program by closing the communications
  19103.  channel, restoring the display screen to full size and normal attribute,
  19104.  and homing the cursor. The fourth scan code recognized by DoCommand is
  19105.  that of the PC's Del key. On a terminal, the Del key sends the ASCII DEL
  19106.  code (127), but the PC does not. So DoCommand converts the internal code
  19107.  to the one expected by the remote system.
  19108.  
  19109.  The Dial procedure implements simple keyboard dialing. When the user
  19110.  types Alt-d, the program responds with the prompt "Number:" in the view
  19111.  window and waits for a telephone number to be typed. After receiving a
  19112.  number, the Dial procedure uses the ATDT string (assuming tone dialing on
  19113.  a Hayes(R)-compatible modem) as a prefix for the telephone number and sends
  19114.  the dialing command to the modem.
  19115.  
  19116.  Once connected to the remote system, the user must log in and start a
  19117.  terminal session in the manner expected by the remote host. During a
  19118.  session, it may be necessary to tell the host to stop whatever it is
  19119.  doing, which is usually done by pressing the Break key, if there is one.
  19120.  UNIX and XENIX systems will accept a Del key for this purpose, too. The
  19121.  BreakSignal procedure, initiated by the Alt-b command, sends a true break
  19122.  signal to the host to get its attention. BreakSignal works by setting the
  19123.  break bit in the communication port control register, holding it high for
  19124.  a period of time that exceeds that of a normal character transmission
  19125.  period, and then clearing the break bit.
  19126.  
  19127.  The delay period, fixed at a half second in this implementation, is
  19128.  created by the Delay procedure, which uses the BASIC TIMER function in a
  19129.  loop to control the period. Delay gets the starting time and adds the
  19130.  requested period to it, then continually checks for the arrival of the
  19131.  future time specified by the sum. To prevent machine lockup at midnight,
  19132.  or whenever the clock on the PC rolls over its count of seconds to 0, the
  19133.  Delay procedure aborts if the current time value drops below the starting
  19134.  time.
  19135.  
  19136.  Received data is similarly analyzed as it comes in. Most characters are
  19137.  simply printed on the PC screen in the view window. However, a small set
  19138.  of incoming character codes have special meaning: control codes that cause
  19139.  absolute and relative cursor positioning and screen clearing. The adm3a
  19140.  lacks line and character insert and delete features, so the emulation is
  19141.  simple.
  19142.  
  19143.  If the Esc code is seen, it could signal the start of a cursor-
  19144.  positioning command. The cursor position is set by an escape sequence of
  19145.  the form Esc=rc, where r is a row number and c is a column number, each
  19146.  encoded as a character, and the ASCII space (decimal 32) represents row or
  19147.  column 0. The logic of the ADM3A program detects the Esc code and looks
  19148.  for the equal sign. If one is seen in the next character position, two
  19149.  additional characters are read and converted to row and column numbers
  19150.  relative to the view window values (Top and Left), and the cursor is
  19151.  positioned. If the character following the Esc code is not an equal
  19152.  sign, the program simply prints the escape character and whatever
  19153.  follows it.
  19154.  
  19155.  All other characters are examined by the ProcessInput procedure to see
  19156.  whether they should be printed. Some codes are commands. Ctrl-z, for
  19157.  example, clears the screen. Care is taken to handle boundary conditions
  19158.  as the real adm3a terminal does. A request to move the cursor right when
  19159.  it is already at the right window border, for example, uses a LOCATE
  19160.  statement to implement the automatic margins feature, which moves the
  19161.  cursor to the beginning of the next line, if there is one.
  19162.  
  19163.  Debugging and testing is aided greatly by the built-in Microsoft
  19164.  QuickBASIC debugging features. Figure 10 shows the screen appearance
  19165.  after a watch expression, CmdKey$, and a breakpoint on the SELECT CASE
  19166.  statement have been set. When you run the program, it stops at the
  19167.  breakpoint so you can observe the value of the watch expression. You can
  19168.  have multiple watch expressions and breakpoints. Press F5 to continue
  19169.  execution after it stops at a breakpoint.
  19170.  
  19171.  The sample session depicted in Figure 11 shows the ADM3A program accessing
  19172.  a XENIX system. It is running the VI editor on the /etc/termcap file,
  19173.  showing the termcap entry for the adm3a terminal, among others. Any other
  19174.  program that knows how to interact with an adm3a terminal, and most of
  19175.  them do, can run successfully with this emulation.
  19176.  
  19177.  Programs that use fancy line-drawing characters, inverse video, and
  19178.  special character formatting will not look too hot on an adm3a and may
  19179.  not even run at all. You can use this emulation as a starting point for
  19180.  more sophisticated emulations, such as DEC VT100-series terminals. Also,
  19181.  you may want to add file transfer and other "intelligent terminal"
  19182.  features to round out the program. Microsoft QuickBASIC has what it takes
  19183.  to do that and much more.
  19184.  
  19185.  
  19186.  Welcome to the Future
  19187.  
  19188.  The technology in Microsoft QuickBASIC offers many benefits to its users.
  19189.  Microsoft QuickBASIC ushers in the era of significantly higher
  19190.  programmer productivity and injects a great deal of fun into what has
  19191.  traditionally been drudgery.
  19192.  
  19193.  Where to from here? Microsoft representatives have said publicly that a
  19194.  significant amount of in-house work on current and future language products
  19195.  is based on p-code technology, which will be the basis of compiler products
  19196.  for other languages and some application programs. I hope that these
  19197.  products will be announced in the near future.
  19198.  
  19199.  
  19200.  Figure 2:  The Microsoft QuickBASIC Development Cycle
  19201.  
  19202.            ┌─────────────────────────────────────────────────┐
  19203.            │             Microsoft QuickBASIC 3.0            │
  19204.            │   ╔════════════╗               ╔════════════╗   │
  19205.            │   ║    EDIT    ╠══════════════║  COMPILE   ║   │
  19206.            │   ║            ║               ║            ║   │
  19207.            │   ╚═══════════╝══════╗       ╚══════╦═════╝   │
  19208.            │         ║              ║              ║         │
  19209.            │   ╔═════╩══════╗       ╚═══════╦═══════════╗   │
  19210.            │   ║   DEBUG    ║               ║    RUN     ║   │
  19211.            │   ║            ║══════════════╣            ║   │
  19212.            │   ╚════════════╝               ╚════════════╝   │
  19213.            ├─────────────────────────────────────────────────┤
  19214.            │             Microsoft QuickBASIC 4.0            │
  19215.            │   ╔════════════╗               ╔════════════╗   │
  19216.            │   ║    EDIT    ║═══════╗       ║  COMPILE   ║   │
  19217.            │   ║            ║       ║       ║            ║   │
  19218.            │   ╚════════╦══╝════╗ ║       ╚════════════╝   │
  19219.            │      ║      ║        ║ ║                        │
  19220.            │   ╔══╩════════╗     ║ ╚══════╔════════════╗   │
  19221.            │   ║   DEBUG    ║     ╚═════════╣    RUN     ║   │
  19222.            │   ║            ║══════════════╣            ║   │
  19223.            │   ╚════════════╝               ╚════════════╝   │
  19224.            └─────────────────────────────────────────────────┘
  19225.  
  19226.  
  19227.  Figure 3:  Microsoft QuickBASIC 4.0 p-code
  19228.  
  19229.         ┌───────────────────────────────────────────────────┬────────┐
  19230.         │                  ╔═════════════════════╗          │        │
  19231.         │       ┌─────────║       ASCII         ║          │  Disk  │
  19232.         │       │          ║       TEXT          ║          │   or   │
  19233.         │       │          ╚════════════════╤════╝          │ Screen │
  19234.         ├───────│─────────────────────────PARSER────────────├────────┤
  19235.         │       │                                          │        │
  19236.         │       │          ╔═════════════════════╗          │   M    │
  19237.         │       │          ║       PARSED        ║          │        │
  19238.         │       │─────────╢                     ║          │   e    │
  19239.         │       │          ╚════════════════╤════╝          │        │
  19240.         │     LISTER              BINDER                  │   m    │
  19241.         │       │          ╔════╧════════════════╗          │        │
  19242.         │       │          ║      SYMBOLIC       ║          │   o    │
  19243.         │       │─────────╢                     ║          │        │
  19244.         │       │          ╚════════════════╤════╝          │   r    │
  19245.         │       │                 BINDER                  │        │
  19246.         │       │          ╔════╧════════════════╗          │   y    │
  19247.         │       │          ║      THREADED       ║          │        │
  19248.         │       └──────────╢                     ║          │        │
  19249.         │                  ╚═════════════════════╝          │        │
  19250.         └───────────────────────────────────────────────────┴────────┘
  19251.  
  19252.  
  19253.  Figure 4:  The Microsoft QuickBASIC 4.0 Programming Environment
  19254.  
  19255.  ╔═════════╗
  19256.  ║   BAS   ╟────┐
  19257.  ╚═════════╝    │                                 ┌─────────┐    ╔═════════╗
  19258.                 │                      ┌─────────│   LIB   ├───║   LIB   ║
  19259.  ╔═════════╗    │   ┌────────┐     ┌───┴────┐     └────┬────┘    ╚═════════╝
  19260.  ║   BI    ╟──┐ └──│        │     │        │          │
  19261.  ╚═════════╝  └────│        │     │        │     ┌────────┐    ╔═════════╗
  19262.                     │   QB   ├────│   BC   ├────│   LINK  ├───║   EXE   ║
  19263.  ╔═════════╗  ┌────│        │     │        │     └─────────┘    ╚═════════╝
  19264.  ║   MAK   ╟──┘ ┌──│        │     │        │
  19265.  ╚═════════╝    │   └────────┘     └────────┘
  19266.                 │
  19267.  ╔═════════╗    │
  19268.  ║   QLB   ╟────┘
  19269.  ╚═════════╝
  19270.  
  19271.  
  19272.  Figure 5:  Mixed-Language Programming
  19273.  
  19274.    ╔═════════╗  ┌────────┐
  19275.    ║   BAS   ╟─│        │
  19276.    ╚═════════╝  │   BC   │
  19277.                 │        │   ╔═════════╗
  19278.    ╔═════════╗  │        │──║   OBJ   ╟───────┐
  19279.    ║    BI   ╟─└────────┘   ╚═════════╝       │
  19280.    ╚═════════╝                                 │
  19281.                                                │
  19282.    ╔═════════╗  ┌────────┐                 ┌───────┐  ╔═════════╗  ┌──────┐
  19283.    ║    C    ╟─│        │                 │        │─║   EXE   ╟─│      │
  19284.    ╚═════════╝  │   CL   │   ╔═════════╗   │        │  ╚═════════╝  │      │
  19285.                 │        │──║   OBJ   ╟──│  LINK  │  ╔═════════╗  │  CV  │
  19286.    ╔═════════╗  │        │   ╚═════════╝   │        │─║   QLB   ╟─│      │
  19287.    ║    H    ╟─└────────┘                 └───────┘  ╚═════════╝  └──────┘
  19288.    ╚═════════╝                                 │
  19289.                                                │
  19290.    ╔═════════╗  ┌────────┐   ╔═════════╗       │
  19291.    ║   ASM   ╟─│  MASM  │──║   OBJ   ╟───────┘
  19292.    ╚═════════╝  └────────┘   ╚═════════╝
  19293.  
  19294.  
  19295.  Figure 6:  Example of the TYPE Keyword
  19296.  
  19297.  TYPE CardType
  19298.  
  19299.       PartNumber AS STRING * 20
  19300.       Description AS STRING * 40
  19301.       UnitCost AS SINGLE
  19302.       StockQty AS INTEGER
  19303.       ReorderQty AS INTEGER
  19304.       CurrentQty AS INTEGER
  19305.       Supplier AS SupplyType
  19306.  END TYPE
  19307.  
  19308.  
  19309.  Figure 7:  Video Terminal Emulator
  19310.  
  19311.  ' ADM3A Terminal Emulator
  19312.  ' Version 1.0
  19313.  '
  19314.  ' This program emulates a Lear Siegler adm3a video terminal.  The
  19315.  ' emulator gives PC users the ability to run full-screen video
  19316.  ' programs on UNIX and XENIX systems, and others that support a
  19317.  ' video terminal interface.
  19318.  '
  19319.  ' Author: Augie Hansen
  19320.  ' Released: 1-14-88
  19321.  
  19322.  
  19323.  DEFINT A-Z
  19324.  
  19325.  '─ Subprogram declarations.
  19326.  DECLARE SUB BreakSignal ()
  19327.  DECLARE SUB Delay (Period!)
  19328.  DECLARE SUB Dial ()
  19329.  DECLARE SUB DoCommand (CmdKey$)
  19330.  DECLARE SUB InitScreen ()
  19331.  DECLARE SUB ProcessInput (Code$)
  19332.  
  19333.  '─ Manifest constants.
  19334.  CONST TRUE = -1, FALSE = NOT TRUE
  19335.  CONST BLACK = 0, BLUE = 1, GREEN = 2, CYAN = 3
  19336.  CONST RED = 4, MAGENTA = 5, BROWN = 6, WHITE = 7
  19337.  CONST BRIGHT = 8, BLINK = 128
  19338.  CONST CURSOROFF = 0, CURSORON = 1
  19339.  CONST BUFSIZE = 512
  19340.  CONST SPACE = 32
  19341.  CONST ROWS = 25, COLS = 80
  19342.  CONST BANNERCOL = 4, COMMANDCOL = 33
  19343.  CONST TESTMODE = 0
  19344.  
  19345.  '─ Screen management data.
  19346.  TYPE WinType
  19347.       Top AS INTEGER
  19348.       Left AS INTEGER
  19349.       Bottom AS INTEGER
  19350.       Right AS INTEGER
  19351.       Fgnd AS INTEGER
  19352.       Bkgnd AS INTEGER
  19353.       Standout AS INTEGER
  19354.  END TYPE
  19355.  
  19356.  DIM CmdWin AS WinType
  19357.  DIM ViewWin AS WinType
  19358.  
  19359.  CmdWin.Top = 1
  19360.  CmdWin.Bottom = 1
  19361.  CmdWin.Left = 1
  19362.  CmdWin.Right = 80
  19363.  CmdWin.Fgnd = BLACK
  19364.  CmdWin.Bkgnd = WHITE
  19365.  CmdWin.Standout = BROWN + BRIGHT
  19366.  
  19367.  ViewWin.Top = 2
  19368.  ViewWin.Bottom = 25
  19369.  ViewWin.Left = 1
  19370.  ViewWin.Right = 80
  19371.  ViewWin.Fgnd = WHITE
  19372.  ViewWin.Bkgnd = BLUE
  19373.  ViewWin.Standout = WHITE + BRIGHT
  19374.  
  19375.  '─ Set cursor-positioning offsets.
  19376.  RowOffset = SPACE - ViewWin.Top
  19377.  ColOffset = SPACE - ViewWin.Left
  19378.  
  19379.  '─ Install an error-recovery mechanism.
  19380.  ON ERROR GOTO ErrorRecovery
  19381.  
  19382.  '─ Set up the emulator screen.
  19383.  InitScreen
  19384.  
  19385.  '─ Set communications parameters.
  19386.  Parm$ = ENVIRON$("COMPARMS")            ' Check environment.
  19387.  IF Parm$ = "" THEN
  19388.       Parm$ = "COM2:1200,E,7,1"          ' Use defaults.
  19389.  END IF
  19390.  
  19391.  Port$ = LEFT$(Parm$, 4)
  19392.  IF Port$ = "COM1" THEN
  19393.       PortAddress = &H3FB
  19394.  ELSE
  19395.       PortAddress = &H2FB
  19396.  END IF
  19397.  BreakMask = &H40                        ' Break control bits
  19398.  
  19399.  '─ Open the communications channel.
  19400.  OPEN Parm$ FOR RANDOM AS #1 LEN = BUFSIZE
  19401.  
  19402.  '
  19403.  ' Main communications loop.
  19404.  '
  19405.  ' Check the keyboard for input. Send all normal characters typed by
  19406.  ' the user to the communications port for transmission to the remote
  19407.  ' system. If the user presses any of the emulator command keys, run
  19408.  ' the associated procedure.
  19409.  '
  19410.  Main:
  19411.  EscapeFlag = FALSE
  19412.  DO
  19413.       '─ Process keyboard input for commands and characters.
  19414.       UserKey$ = INKEY$
  19415.       IF LEN(UserKey$) > 1 THEN
  19416.            DoCommand UserKey$
  19417.       ELSEIF UserKey$ <> "" THEN
  19418.            '─ Send the character to the remote system.
  19419.            PRINT #1, UserKey$;
  19420.       END IF
  19421.  
  19422.       '─ Check the communications line for received characters.
  19423.       DO
  19424.            IF EOF(1) THEN
  19425.                    EXIT DO
  19426.            END IF
  19427.            Received$ = INPUT$(1, #1) ' Read a single character.
  19428.  
  19429.            '─ Look for cursor-positioning command.
  19430.            IF EscapeFlag = TRUE THEN
  19431.                    IF Received$ = "=" THEN
  19432.                    CursorRow = ASC(INPUT$(1, #1))-RowOffset
  19433.                    CursorCol = ASC(INPUT$(1, #1))-ColOffset
  19434.                    LOCATE CursorRow, CursorCol
  19435.                    ELSE
  19436.                    PRINT CHR$(27); ' The retained Esc code.
  19437.                    PRINT Received$;
  19438.                    END IF
  19439.                    EscapeFlag = FALSE
  19440.            ELSE
  19441.                    ProcessInput Received$
  19442.            END IF
  19443.       LOOP
  19444.  LOOP
  19445.  
  19446.  END
  19447.  
  19448.  ErrorRecovery:
  19449.       RESUME Main
  19450.  
  19451.  '
  19452.  ' BreakSignal
  19453.  '
  19454.  ' Send a 'break' signal to the communications port.
  19455.  '
  19456.  '
  19457.  SUB BreakSignal
  19458.       SHARED PortAddress, BreakMask
  19459.  
  19460.       '─ Set the break bit.
  19461.       OUT PortAddress, (INP(PortAddress) OR BreakMask)
  19462.  
  19463.       '─ Mark time for the break period.
  19464.       Delay .5
  19465.  
  19466.       '─ Clear the break bit.
  19467.       OUT PortAddress, (INP(PortAddress) AND NOT BreakMask)
  19468.  END SUB
  19469.  
  19470.  '
  19471.  ' Delay
  19472.  '
  19473.  ' Produce a specified delay.  The delay period is specified in
  19474.  ' seconds as a single-precision number with tenth-second precision.
  19475.  '
  19476.  '
  19477.  SUB Delay (Period!) STATIC
  19478.       Start! = TIMER
  19479.  
  19480.       '─ Loop for specified period.  Abort if clock rolls over.
  19481.       DO
  19482.            Now! = TIMER
  19483.            IF (Now! - Start! < Period!) OR (Now! < Start!) THEN
  19484.                    EXIT SUB
  19485.            END IF
  19486.       LOOP
  19487.  END SUB
  19488.  
  19489.  '
  19490.  ' Dial
  19491.  '
  19492.  ' Ask the user for a telephone number and dial it.
  19493.  '
  19494.  '
  19495.  SUB Dial
  19496.       INPUT "Number: ", Phone$
  19497.       PRINT #1, "ATDT" + Phone$
  19498.  END SUB
  19499.  
  19500.  '
  19501.  ' DoCommand
  19502.  '
  19503.  ' Examine the extended key code to see whether it is an Emulator
  19504.  ' program command.  If it is, execute the requested command.  If it
  19505.  ' is not, return to the caller without doing anything.
  19506.  '
  19507.  '
  19508.  SUB DoCommand (CmdKey$) STATIC
  19509.       SHARED CmdWin AS WinType
  19510.       SELECT CASE ASC(RIGHT$(CmdKey$, 1))
  19511.            CASE 16 ' Alt+q - Quit the emulator.
  19512.                    ' Close the communications channel.
  19513.                    CLOSE
  19514.                    ' Restore full screen.
  19515.                    VIEW PRINT
  19516.                    ' Clear the screen and "home" the cursor.
  19517.                    COLOR WHITE, BLACK
  19518.                    CLS
  19519.                    LOCATE CmdWin.Top, CmdWin.Left, CURSORON
  19520.                    END
  19521.            CASE 32 ' Alt+d - Dial a number
  19522.                    Dial
  19523.            CASE 48 ' Alt+b - Send break signal.
  19524.                    BreakSignal
  19525.            CASE 83 ' PC keyboard Del key - Send an ASCII DEL
  19526.                    PRINT #1, CHR$(127);
  19527.            CASE ELSE
  19528.                    ' Unknown command - ignore it.
  19529.       END SELECT
  19530.  END SUB
  19531.  
  19532.  '
  19533.  ' InitScreen
  19534.  '
  19535.  ' Set up command bar (1 line), guarantee that the cursor is turned
  19536.  ' on, and establish the active terminal display window (24 lines).
  19537.  '
  19538.  '
  19539.  SUB InitScreen STATIC
  19540.       SHARED CmdWin AS WinType, ViewWin AS WinType
  19541.  
  19542.       '─ Initialize the screen for text and 80 columns.
  19543.       SCREEN TEXTMODE
  19544.       WIDTH COLS, ROWS
  19545.       COLOR WHITE, BLACK
  19546.       CLS
  19547.  
  19548.       '─ Draw the command window on the top line.
  19549.       LOCATE CmdWin.Top, CmdWin.Left, CURSORON
  19550.       COLOR CmdWin.Fgnd, CmdWin.Bkgnd
  19551.       PRINT SPACE$(CmdWin.Right - CmdWin.Left + 1)
  19552.  
  19553.       '─ Display the program banner.
  19554.       LOCATE CmdWin.Top, CmdWin.Left + BANNERCOL
  19555.       COLOR CmdWin.Standout, CmdWin.Bkgnd
  19556.       PRINT "ADM3A EMULATOR";
  19557.  
  19558.       '─ Display a command summary.
  19559.       LOCATE CmdWin.Top, CmdWin.Left + COMMANDCOL
  19560.       COLOR CmdWin.Fgnd, CmdWin.Bkgnd
  19561.       PRINT "Break (Alt+b)   Dial (Alt+d)   Quit (Alt+q)"
  19562.  
  19563.       '─ Initialize the terminal screen.
  19564.       VIEW PRINT ViewWin.Top TO ViewWin.Bottom
  19565.       COLOR ViewWin.Fgnd, ViewWin.Bkgnd
  19566.       CLS
  19567.  END SUB
  19568.  
  19569.  '
  19570.  ' ProcessInput
  19571.  '
  19572.  ' Check input from the communications line and analyze it.  Act on
  19573.  ' any adm3a terminal commands codes.  Pass anything else unchanged
  19574.  ' to the terminal screen.
  19575.  '
  19576.  '
  19577.  SUB ProcessInput (Code$) STATIC
  19578.       SHARED EscapeFlag, ViewWin AS WinType
  19579.       SELECT CASE ASC(Code$)
  19580.            CASE 8 ' ASCII backspace character
  19581.                    IF POS(0) > ViewWin.Left THEN
  19582.                      ' Nondestructive backspace
  19583.                      LOCATE , POS(0) - 1
  19584.                    END IF
  19585.            CASE 10 ' ^J - New-line character
  19586.                    IF CSRLIN < ViewWin.Bottom THEN
  19587.                      LOCATE CSRLIN + 1
  19588.                    ELSE
  19589.                      PRINT Code$;
  19590.                    END IF
  19591.            CASE 11 ' ^K - Up-line command
  19592.                    IF CSRLIN > ViewWin.Top THEN
  19593.                      LOCATE CSRLIN - 1
  19594.                    END IF
  19595.            CASE 12 ' ^L - Form-feed character
  19596.                    ' adm3a use as nondestructive space
  19597.                    IF POS(0) < ViewWin.Right THEN
  19598.                      LOCATE , POS(0) + 1
  19599.                    ELSEIF (POS(0) = ViewWin.Right) AND _
  19600.                           (CSRLIN < ViewWin.Bottom) THEN
  19601.                      LOCATE CSRLIN + 1, ViewWin.Left
  19602.                    END IF
  19603.            CASE 13
  19604.                    LOCATE , ViewWin.Left
  19605.            CASE 26 ' ^Z - Clear the screen
  19606.                    CLS
  19607.            CASE 27 ' Esc - Could be start of cursor sequence
  19608.                    EscapeFlag = TRUE
  19609.            CASE 30 ' ^^ - Cursor to home position
  19610.                    LOCATE ViewWin.Top, ViewWin.Left
  19611.            CASE ELSE
  19612.                    PRINT Code$;
  19613.       END SELECT
  19614.  END SUB
  19615.  
  19616.  ████████████████████████████████████████████████████████████████████████████
  19617.  
  19618.  Debug Microsoft Windows Programs More Effectively with a Simple Utility
  19619.  
  19620.  Kevin P. Welch
  19621.  
  19622.  Debugging is an indispensable and unavoidable step in the development of
  19623.  any software program. With conventional MS-DOS(R) programs, debugging
  19624.  usually starts with the incorporation of numerous print statements into
  19625.  the program. If this doesn't work, the battle escalates to sessions with
  19626.  DEBUG, CodeView(R), or even to utilizing a hardware-level debugger.
  19627.  
  19628.  Unfortunately for Windows programmers, many of these traditional
  19629.  techniques won't work in the Windows environment. The last few months have
  19630.  seen significant improvements in Windows debugging──most notably the
  19631.  announcement of CodeView for Windows──but programmers continue to struggle
  19632.  with the difficult task of debugging and fine-tuning Windows
  19633.  applications.
  19634.  
  19635.  
  19636.  Approaches to Debugging
  19637.  
  19638.  From a theoretical perspective, there are many ways to classify software
  19639.  debugging methods. One common approach breaks debugging into three
  19640.  general categories of invasive, noninvasive, and combined techniques.
  19641.  
  19642.  Invasive debugging typically involves modifying and recompiling
  19643.  application source code. The debugging tools and their entry points simply
  19644.  become part of the program, which makes the technique easy to implement.
  19645.  Two examples of this approach are hard-coded message boxes and print
  19646.  statements whose output is directed to display or temporary log files.
  19647.  
  19648.  This approach often suffices to catch a large proportion of coding and
  19649.  algorithmic errors. With skilled use, this method can serve as the major
  19650.  debugging tool for large and complicated applications.
  19651.  
  19652.  However, this approach requires that you have access to the source code in
  19653.  question (a problem when you are debugging an interaction with a program
  19654.  you didn't write). In addition, the mere inclusion of the debugging
  19655.  statements or tools into the application sometimes changes the very
  19656.  character of the problem, further complicating the situation. This is
  19657.  especially true with system-related issues like memory management and
  19658.  interprocess communication.
  19659.  
  19660.  Noninvasive debugging does not involve modification or recompilation of
  19661.  an application. The debugging tool and its associated control functions
  19662.  are external to the application and can usually be applied to an existing
  19663.  program. Some examples of this approach include message loggers
  19664.  (essentially a form of application subclassing), journaling hooks, alias
  19665.  libraries, execution profilers, and even strict hardware-level
  19666.  debuggers.
  19667.  
  19668.  If used correctly, the noninvasive approach can provide a great deal of
  19669.  externally derived diagnostic information. This technique is especially
  19670.  useful in debugging complex interactions between applications.
  19671.  Furthermore, using debugging tools that are external to the
  19672.  application provides a limit on the possible compounding of errors.
  19673.  
  19674.  On the negative side, this approach is often complicated, it only reports
  19675.  on externally discernable events, and it tends to generate quantities of
  19676.  unimportant information surrounding a few vital pieces of data. The
  19677.  programmer usually has to do the interpreting of events reported, sorting
  19678.  and searching for what is really important.
  19679.  
  19680.  Combined debugging uses both invasive and noninvasive techniques. In most
  19681.  cases, the program need only be recompiled with a different compiler
  19682.  switch, which generates symbol files or other output useful to the
  19683.  debugging tool. Some examples of this approach include CodeView, Symdeb,
  19684.  and other more complicated hardware-debugging tools capable of working
  19685.  with application internals.
  19686.  
  19687.  This approach probably represents the most powerful of these debugging
  19688.  techniques, and is capable of capturing and describing even the most
  19689.  insidious coding or system error. Also, while tracking the problem the
  19690.  programmer can usually inspect internal data structures, which provides
  19691.  even greater insight into a given situation.
  19692.  
  19693.  To use this approach effectively, the programmer needs to have an
  19694.  intimate understanding of both the underlying operating system and the
  19695.  hardware. The power of this technique is offset by a steep learning curve,
  19696.  and the generation of a great deal of information. Many programmers
  19697.  hesitate to use it except for the most puzzling and difficult of
  19698.  problems.
  19699.  
  19700.  
  19701.  Windows Debug Utility
  19702.  
  19703.  After developing a number of Windows applications and struggling with
  19704.  the problems of debugging, I decided to develop a simple invasive
  19705.  debugging tool that would provide reasonable insight into the operations
  19706.  that occurred within a troublesome application.
  19707.  
  19708.  This effort resulted in the Windows Debug utility, a small object module
  19709.  (about 4Kb compiled) that can be conditionally linked into your
  19710.  program. It provides print statementlike debugging capabilities within the
  19711.  Windows environment.
  19712.  
  19713.  Once integrated into your application, the tools provided by the utility
  19714.  can easily be used to display or record a variety of internal program
  19715.  events. The entire debugging process is managed with a central control
  19716.  panel (see Figure 1) accessible through the system menu of the
  19717.  application. Using this control panel you can:
  19718.  
  19719.    ■  turn debugging ON or OFF
  19720.    ■  filter out unwanted debugging events
  19721.    ■  direct debugging output to the display and/or a log file
  19722.  
  19723.  
  19724.  Debug Utility Functions
  19725.  
  19726.  The debug module linked into your application is built from three separate
  19727.  files. Using the files DEBUG (make file for debug module), DEBUG.H (header
  19728.  file containing definitions and function declarations), and DEBUG.C
  19729.  (source code for debug utilities), you can create the object module and
  19730.  integrate it into your program. (Due to space limitations, the code for
  19731.  the DEBUG utility could not be included. It must be downloaded from any
  19732.  of our bulletin boards. -Ed.)
  19733.  
  19734.  The debug module consists of four functions, three of which are referenced
  19735.  explicitly within your host application.
  19736.  
  19737.  The first of these is the DebugSetup function, which is responsible for
  19738.  creating the debug viewport window and appending the Debug... control
  19739.  panel menu option to the system menu of the main application window. To
  19740.  call this function you must provide a window handle, a debug control panel
  19741.  message, and the maximum number of lines to be maintained by the debug
  19742.  viewport.
  19743.  
  19744.  The DebugSetup function assumes that there is a system menu associated
  19745.  with the main application window handle that you provide. If your main
  19746.  window does not contain a system menu and you wish to use some other
  19747.  means to provide access to the debug control panel, you can easily do so.
  19748.  
  19749.  Whenever the Debug... option is appended to the system menu (see
  19750.  Figure 2), the control panel message number is associated with the option
  19751.  and is returned. The actual message number is returned in the wParam
  19752.  portion of the WM_SYSCOMMAND message. To display the control panel you
  19753.  have to explicitly trap this message and call the DebugControl function.
  19754.  
  19755.  The parameter specifying maximum viewport lines in DebugSetup limits the
  19756.  amount of memory consumed by the utility while you view debug statements
  19757.  in the viewport. The debug viewport consists of a movable listbox
  19758.  containing a series of formatted debugging statements. Debugging
  19759.  statements displayed in this viewport are successively appended until
  19760.  the specified maximum is reached. At that point, each additional
  19761.  debugging statement displaces an earlier one. Although the maximum
  19762.  number of debugging statements maintained by the viewport is limited
  19763.  only by memory, in most situations you should limit the number to less
  19764.  than 100.
  19765.  
  19766.  The DebugControl function lets you display the debug control panel and
  19767.  change the current debugging options. This function is normally called as
  19768.  part of the WM_SYSCOMMAND message handling code, which is invoked whenever
  19769.  the user selects the Debug... option from the system menu. This function
  19770.  calls DebugControlDlgFn using the predefined DebugControl dialog box
  19771.  resource.
  19772.  
  19773.  DebugControlDlgFn is responsible for processing all the messages related
  19774.  to the debug control panel dialog box. The initialization section (under
  19775.  WM_INITDIALOG) of this function translates the current global debug-
  19776.  options data structure into corresponding button states and edit fields.
  19777.  The command section (under WM_COMMAND) processes the various button
  19778.  messages and accordingly enables or disables fields. The termination
  19779.  section (under BUG_OK) retrieves the current control panel status and
  19780.  translates it back into the global options data structure. At the same
  19781.  time it parses (albeit in a crude manner) the filter edit field and
  19782.  defines a list of accepted debug statement classification codes.
  19783.  
  19784.  Finally, the Debug function generates formatted debugging statements and
  19785.  is typically called throughout your application. The first parameter to
  19786.  this function is the statement classification code, which is used by
  19787.  the filter section of the Debug function. If the debug filter is active,
  19788.  this classification code is checked against a list of to-be-filtered
  19789.  categories. Only messages with codes on this list are displayed or
  19790.  written to disk, enabling you to filter out messages you're not
  19791.  interested in.
  19792.  
  19793.  For example, you could arbitrarily assign a classification code of 1 to
  19794.  all paint-related events or processes, a code of 2 to clipboard-related
  19795.  ones, and a code of 3 to all others. If you wished to exclude all but
  19796.  clipboard- and paint-related debugging statements, you would activate
  19797.  the filter from the control panel with a range of 1,2. All statements
  19798.  assigned code 3 would be excluded from the viewport or log-file listing,
  19799.  yielding an abbreviated execution profile that would let you concentrate
  19800.  on particular facets of your program.
  19801.  
  19802.  The second parameter to the Debug function is a format control string
  19803.  defining the physical contents of the resulting statement. This control
  19804.  string is identical to that used by the standard C printf function. The
  19805.  parameters following this format string are used in conjunction with
  19806.  the format control string and are converted and output accordingly.
  19807.  Figure 3 illustrates how the Debug function might be used.
  19808.  
  19809.  Note how the variable number of parameters is handled by the Debug
  19810.  function. Unless you increase the size of the pass-by-value structure, you
  19811.  are limited to 64 bytes of parameter storage. You may wish to experiment
  19812.  with alternative ways of handling the parameter list. One suggestion is
  19813.  to use the C 5.0 vprintf function along with either the ANSI C or UNIX(R)
  19814.  System V variants.
  19815.  
  19816.  
  19817.  Integrating the Debug Utility
  19818.  
  19819.  Before you can use the tools provided by the debug module to probe the
  19820.  workings of your application, you must perform several steps.
  19821.  
  19822.  Copy the following files to the directory where you are creating your
  19823.  application:
  19824.  
  19825.    DEBUG.H
  19826.    DEBUG.C
  19827.  
  19828.  Define a new compiler command line variable in your application make
  19829.  file. This variable can be used within a conditional compilation
  19830.  pragma, thus controlling the generation of debugging code. For example,
  19831.  you might define and use the compiler flags seen in Figure 4.
  19832.  
  19833.  Modify your application's make file so that the module DEBUG.OBJ is
  19834.  included whenever you build your program. See Figure 5 for an example.
  19835.  
  19836.  Define a debug control message number in your application header file.
  19837.  This message is returned by Windows whenever you select the Debug...
  19838.  option from the system menu.
  19839.  
  19840.  Export DebugControlDlgFn by defining it in your application DEF file.
  19841.  Failure to do this will result in erratic behavior. For example, you might
  19842.  change the export section of your DEF file from
  19843.  
  19844.    EXPORTS
  19845.      TestWndFn    @1
  19846.  to
  19847.  
  19848.    EXPORTS
  19849.      TestWndFn    @1
  19850.      DebugControlDlgFn @2
  19851.  
  19852.  Add the lines shown in Figure 6 to your application's resource file.
  19853.  
  19854.  Call the DebugSetup function shortly after creating your main application
  19855.  window. Be sure your main window has a system menu and that you pass a
  19856.  valid handle to this function. Note that it is good practice to enclose
  19857.  this function call within a conditional compilation pragma:
  19858.  
  19859.    #if DEBUG
  19860.      DebugSetup( hMainWnd, BUG_CONTROL, 100 );
  19861.    #endif
  19862.  
  19863.  Include the DEBUG.H file in each source code module for which you use
  19864.  routines from the debug utility. The header file includes function
  19865.  definitions and calling conventions necessary to the routines.
  19866.  
  19867.  Define debugging statements throughout your program in areas of particular
  19868.  interest. Try to categorize your debugging statements.
  19869.  
  19870.  Note that debugging by default is OFF and that statements encountered
  19871.  before it is explicitly activated are ignored. One example of this is the
  19872.  use of debugging statements to probe the CREATEWINDOW message
  19873.  processing section of your main window function.
  19874.  
  19875.  Recompile and link your application.
  19876.  
  19877.  
  19878.  Using the Debug Utility
  19879.  
  19880.  To get the best understanding of the debug utility let's look at a sample
  19881.  application like BUGTEST.EXE. Figure 7 shows how to create this
  19882.  application, with each file containing references to the debug utility.
  19883.  
  19884.  Throughout the journal listings of these files the sections which
  19885.  reference the debug utility are clearly identified. You can use these
  19886.  changes as a template when you incorporate the utility into your own
  19887.  applications.
  19888.  
  19889.  Pay special attention to the TestWndFn in the BUGTEST.C file (see Figure 8
  19890.  for the code listings for BUGTEST). This window function contains several
  19891.  explicit references to the debug utility. First you see a series of
  19892.  debugging statements that output the contents of several Windows messages.
  19893.  Note how the messages are classified and remember that you can filter
  19894.  categories to limit the statements generated.
  19895.  
  19896.  You should also notice the special handling of the BUG_CONTROL message in
  19897.  the WM_SYSCOMMAND processing section of the function. This message is
  19898.  generated whenever you access the Debug... option from the system menu.
  19899.  The call to DebugControl displays the debug control panel from which you
  19900.  can activate or deactivate debugging and define various output options.
  19901.  
  19902.  To create BUGTEST.EXE you need Microsoft C 5.0, Windows 2.03, and the
  19903.  Windows 2.03 Software Development Kit (SDK). If you don't have the 2.03
  19904.  SDK, you can probably get everything to work correctly (with perhaps minor
  19905.  modifications) using the 1.04 SDK. Although the BUGTEST.ICO file is not
  19906.  included in the code listings, you can easily create your own icon with
  19907.  the editor in the Windows SDK.
  19908.  
  19909.  Once you have built the BUGTEST application, you can experiment with the
  19910.  debug utility. When you first bring up the program, a standard pop-up
  19911.  window appears. Using either the mouse or the keyboard you can move,
  19912.  resize, and make this window into an icon just as you would any other
  19913.  window. Whenever you perform one of these operations, numerous debugging
  19914.  statements are generated, but they are ignored by the debug utility until
  19915.  you explicitly turn debugging ON through the control panel.
  19916.  
  19917.  To access the control panel you select the Debug... option from the system
  19918.  menu. The debug control panel is displayed. From this dialog box you can
  19919.  activate debugging and define options. You can check the count and display
  19920.  boxes, which causes debugging statements to be enumerated and the results
  19921.  displayed in a pop-up listbox (see Figure 9). Using this listbox you can
  19922.  watch the debugging statements as they are generated by the main
  19923.  application (see Figure 10).
  19924.  
  19925.  Remember that the filter option allows you to select particular
  19926.  categories of debugging statements. Also note that this implementation of
  19927.  the debug utility is set for 32 filter categories. If these are
  19928.  insufficient, you can easily change the MAX_FILTER definition in DEBUG.C
  19929.  to a larger number and recompile the utility.
  19930.  
  19931.  The log-file option works almost identically to the display option, except
  19932.  that debugging statements are appended to the disk file you specify. You
  19933.  can simultaneously display and log, although your system will slow
  19934.  considerably. When using log files I add the following line to the
  19935.  "extensions section" of my WIN.INI file:
  19936.  
  19937.    LOG=NOTEPAD.EXE ^.LOG
  19938.  
  19939.  This lets you double-click on any log file and view its contents using the
  19940.  notepad provided with Windows. Be aware that notepad is not capable of
  19941.  loading large log files, but this shouldn't be a major problem.
  19942.  
  19943.  
  19944.  Conclusion
  19945.  
  19946.  The debug utility is not about to displace the likes of Windows CodeView.
  19947.  However, for simple debugging it can provide a great deal of insight into
  19948.  the workings of an application and should be a useful addition to every
  19949.  programmer's toolbox.
  19950.  
  19951.  The debug utility is capable of solving a large number of Windows
  19952.  problems. With the code provided here as a framework, you may extend this
  19953.  utility into many interesting areas. You could use journaling hooks to
  19954.  create a general purpose message tracker to explore problems between
  19955.  applications. You could experiment with multiple debug viewports into a
  19956.  single application. Or you could move the utility into a dynamically
  19957.  linked library and debug multiple applications simultaneously. These,
  19958.  and hopefully many other creative enhancements are left to you.
  19959.  
  19960.  
  19961.  Figure 3:  A Sample Use of the DEBUG Utility
  19962.  
  19963.  Debug( 1, "WM_SIZE: [%u,%u], LOWORD(lParam),HIWORD(lParam));
  19964.  Debug( 2, "WM_MOVE: [%u,%u]", LOWORD(lParam),IWORD(lParam));
  19965.  Debug( 3, "WM_CHAR: [%c,%u]", wParam, wParam );
  19966.  Debug( 4, "WM_ACTIVATE: [%s]", (wParam)?"on":"off" );
  19967.  
  19968.  
  19969.  Figure 4:  Sample Compiler Flags for Compiling the DEBUG Utility Code
  19970.  
  19971.  STDFLAGS=-c -u -AS -FPa -Gsw -Os -Zep
  19972.  BUGFLAGS=-c -u -AS -FPa -Gsw -Os -Zep -DDEBUG
  19973.  
  19974.  bugtest.obj: bugtest.c
  19975.  cl $(BUGFLAGS) bugtest.c
  19976.  
  19977.  
  19978.  Figure 5:  Sample Make File to Include DEBUG within an Application
  19979.  
  19980.  Change your make file from
  19981.  
  19982.       bugtest.exe: bugtest.obj bugtest.def bugtest.res
  19983.            link4 bugtest /AL:16 /NOE,,,slibw,bugtest.def
  19984.  
  19985.  to
  19986.  
  19987.       bugtest.exe: bugtest.obj debug.obj bugtest.def bugtest.res
  19988.            link4 bugtest+debug /AL:16 /NOE,,,slibw,bugtest.def
  19989.  
  19990.  
  19991.  Figure 6:  Debug Control Panel Definitions
  19992.  
  19993.  #include "debug.h"
  19994.  
  19995.  DebugControl DIALOG LOADONCALL MOVEABLE DISCARDABLE 8, 20, 185, 81
  19996.  STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_POPUP
  19997.  BEGIN
  19998.   CONTROL "OFF - debug &inactive." BUG_OFF,"button",STD_BUTTON,5,3,130,12
  19999.   CONTROL "ON - debug &active." BUG_ON,"button",STD_BUTTON,5,15,128,12
  20000.   CONTROL "&Count debug events." BUG_COUNT,"button",STD_CHECKBOX,8,26,114,12
  20001.   CONTROL "&Display in debug window." BUG_DISPLAY,"button",STD_CHECKBOX,18,38,
  20002.   CONTROL "&Filter" BUG_FILTER,"button",STD_CHECKBOX,18,50,34,12
  20003.   CONTROL "" BUG_FILTERLIST,"edit",STD_EDITFIELD,55,50,78,12
  20004.   CONTROL "&Log to" BUG_LOG,"button",STD_CHECKBOX,18,62,36,12
  20005.   CONTROL "" BUG_LOGFILE,"edit",STD_EDITFIELD ,55,63,78,12
  20006.   CONTROL "" -1,"static",SS_BLACKFRAME | WS_CHILD,142,0,1,81
  20007.   CONTROL "Ok" BUG_OK,"button",STD_BUTTON,148,4,32,14
  20008.   CONTROL "Cancel" BUG_CANCEL,"button",STD_BUTTON,148,21,32,14
  20009.  END
  20010.  
  20011.  
  20012.  Figure 7:  A list of the files required to build the sample test program that
  20013.             incorporates the DEBUG utility.
  20014.  
  20015.  BUGTEST     application make file
  20016.  BUGTEST.C   application source code
  20017.  BUGTEST.RC  application resource file
  20018.  BUGTEST.ICO application icon (referenced by resource file)
  20019.  BUGTEST.DEF application link definition
  20020.  
  20021.  
  20022.  Figure 8:
  20023.  
  20024.  Make File for BUGTEST
  20025.  
  20026.  STDFLAGS=-c -u -AS -FPa -Gsw -Os -Zep
  20027.  BUGFLAGS=-c -u -AS -FPa -Gsw -Os -Zep -DDEBUG
  20028.  
  20029.  bugtest.res: bugtest.rc bugtest.ico
  20030.    rc -r bugtest.rc
  20031.  
  20032.  debug.obj: debug.h debug.c
  20033.    cl $(STDFLAGS) debug.c
  20034.  
  20035.  bugtest.obj: bugtest.c
  20036.    cl $(BUGFLAGS) bugtest.c
  20037.  
  20038.  bugtest.exe: bugtest.obj bugtest.def bugtest.res debug.obj
  20039.    link4 bugtest+debug /AL:16 /NOE,,,slibw,bugtest.def
  20040.    rc bugtest.res
  20041.  
  20042.  Code Listing for BUGTEST
  20043.  
  20044.  /*
  20045.   * DEBUG TEST PROGRAM - SOURCE CODE
  20046.   *
  20047.   * LANGUAGE    : Microsoft C 5.0
  20048.   * MODEL  : small
  20049.   * STATUS : operational
  20050.   *
  20051.   * 12/11/87 1.00 - Kevin P. Welch - initial creation.
  20052.   *
  20053.   */
  20054.  
  20055.  #include <windows.h>
  20056.  #include "debug.h"
  20057.  
  20058.  /* local definitions */
  20059.  #define   BUG_CONTROL    201
  20060.  
  20061.  /* function definitions */
  20062.  LONG FAR PASCAL     TestWndFn( HWND, WORD, WORD, LONG );
  20063.  
  20064.  /**/
  20065.  
  20066.  /*
  20067.   * MAINLINE - BUG TEST PROGRAM
  20068.   *
  20069.   * This mainline initializes the test program and processes
  20070.   * and dispatches all messages relating to the debug test
  20071.   * window.
  20072.   *
  20073.   */
  20074.  
  20075.  int PASCAL WinMain( hInstance, hPrevInstance, lpsCmd, wCmdShow )
  20076.    HANDLE  hInstance;
  20077.    HANDLE  hPrevInstance;
  20078.    LPSTR   lpsCmd;
  20079.    WORD    wCmdShow;
  20080.  {
  20081.    /* local variables */
  20082.    MSG     Msg; /* current system message */
  20083.  
  20084.    /* initialization */
  20085.    if ( TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow ) ) {
  20086.  
  20087.       /* process system messages until finished */
  20088.       while ( GetMessage( (LPMSG)&Msg, NULL, 0, 0 ) ) {
  20089.            TranslateMessage( (LPMSG)&Msg );
  20090.            DispatchMessage( (LPMSG)&Msg );
  20091.       }
  20092.  
  20093.       /* terminate application */
  20094.       exit( Msg.wParam );
  20095.  
  20096.    } else
  20097.       exit( FALSE );
  20098.  
  20099.  }
  20100.  
  20101.  /**/
  20102.  
  20103.  /*
  20104.   *   TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow ) : BOOL;
  20105.   *
  20106.   *        hInstance      current instance handle
  20107.   *        hPrevInstance  handle to previous instance
  20108.   *        lpsCmd    command line string
  20109.   *        wCmdShow  window display flag
  20110.   *
  20111.   * This utility function performs all the initialization required
  20112.   * for testing the debug utility.  Included in this program is
  20113.   * the registry and creation of the main window & the installation
  20114.   * of the debug utility code.
  20115.   *
  20116.   */
  20117.  
  20118.  static BOOL TestInit( hInstance, hPrevInstance, lpsCmd, wCmdShow )
  20119.       HANDLE         hInstance;
  20120.       HANDLE         hPrevInstance;
  20121.       LPSTR          lpsCmd;
  20122.       WORD      wCmdShow;
  20123.  {
  20124.       /* local variables */
  20125.       HWND      hWnd;     /* current window handle */
  20126.       BOOL      bResult;  /* result of initialization */
  20127.       WNDCLASS  WndClass; /* window class */
  20128.  
  20129.       /* initialization */
  20130.       bResult = FALSE;
  20131.  
  20132.       /* register window class */
  20133.       if ( !hPrevInstance ) {
  20134.  
  20135.            /* define MAZE window class */
  20136.            memset( &WndClass, 0, sizeof(WNDCLASS) );
  20137.            WndClass.lpszClassName = (LPSTR)"TestWindow";
  20138.            WndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
  20139.            WndClass.lpszMenuName = (LPSTR)NULL;
  20140.            WndClass.style = CS_HREDRAW | CS_VREDRAW;
  20141.            WndClass.lpfnWndProc = TestWndFn;
  20142.            WndClass.hInstance = hInstance;
  20143.            WndClass.hIcon = LoadIcon( hInstance, "BugTestIcon" );
  20144.            WndClass.hbrBackground = (HBRUSH)(COLOR_MENU + 1);
  20145.  
  20146.            /* register maze window class */
  20147.            if ( RegisterClass( (LPWNDCLASS)&WndClass ) ) {
  20148.  
  20149.                 /* create window */
  20150.                 hWnd = CreateWindow(
  20151.                      "TestWindow",         /* class name */
  20152.                      "Debug Test Window",  /* caption */
  20153.                      WS_TILEDWINDOW,       /* style */
  20154.                      CW_USEDEFAULT,        /* x position */
  20155.                      CW_USEDEFAULT,        /* y position */
  20156.                      CW_USEDEFAULT,        /* width */
  20157.                      CW_USEDEFAULT,        /* height */
  20158.                      (HWND)NULL,           /* parent window */
  20159.                      (HMENU)NULL,          /* menu */
  20160.                      hInstance,            /* application */
  20161.                      (LPSTR)NULL           /* other data */
  20162.                      );
  20163.  
  20164.                 /* continue if successful */
  20165.                 if ( hWnd ) {
  20166.  
  20167.                 /* Here is where the debug utility is
  20168.                  * installed into the program. A response
  20169.                  * message number is provided along with the
  20170.                  * maximum number of debug statements which
  20171.                  * will be maintained by the listbox. The
  20172.                  * larger this number, the less global memory
  20173.                  * available for your application.
  20174.                  */
  20175.  #if DEBUG
  20176.  
  20177.                 DebugSetup( hWnd, BUG_CONTROL, 100 );
  20178.  #endif
  20179.  
  20180.                 /* make window visible */
  20181.                 bResult = TRUE;
  20182.                 ShowWindow( hWnd, wCmdShow );
  20183.  
  20184.                 }
  20185.  
  20186.            }
  20187.  
  20188.       }
  20189.  
  20190.       /* return result */
  20191.       return( bResult );
  20192.  
  20193.  }
  20194.  
  20195.  /**/
  20196.  
  20197.  /*
  20198.   * TEST WINDOW MESSAGE PROCESSING PROCEDURE
  20199.   *
  20200.   * TestWndFn( hWnd, wMessage, wParam, lParam ) : LONG FAR PASCAL
  20201.   *
  20202.   *        hWnd      window handle
  20203.   *        wMessage       message number
  20204.   *        wParam         additional message information
  20205.   *        lParam         additional message information
  20206.   *
  20207.   * This window function processes all the messages related to
  20208.   * the debug test window.  Using the system menu the user can
  20209.   * display the debug control panel dialog box.
  20210.   *
  20211.   */
  20212.  
  20213.  LONG FAR PASCAL TestWndFn( hWnd, wMessage, wParam, lParam )
  20214.       HWND      hWnd;
  20215.       WORD      wMessage;
  20216.       WORD      wParam;
  20217.       LONG      lParam;
  20218.  {
  20219.       /* local variables */
  20220.       LONG      lResult;  /* result of message */
  20221.  
  20222.       /* initialization */
  20223.       lResult = FALSE;
  20224.  
  20225.  #if DEBUG
  20226.       /* sample debugging output */
  20227.       switch( wMessage )
  20228.            {
  20229.       case WM_MOVE :
  20230.            Debug( 1, "WM_MOVE: [%u,%u]", HIWORD(lParam),
  20231.            LOWORD(lParam) );
  20232.            break;
  20233.       case WM_SIZE :
  20234.            Debug( 1, "WM_SIZE: [%u,%u]", HIWORD(lParam),
  20235.            LOWORD(lParam) );
  20236.            break;
  20237.       case WM_CHAR :
  20238.            Debug( 2, "WM_CHAR: [%c,%u]", wParam, wParam );
  20239.            break;
  20240.       case WM_ACTIVATE :
  20241.            Debug( 3, "WM_ACTIVATE: %s",
  20242.            (wParam)?"activate":"inactivate" );
  20243.            break;
  20244.       case WM_ACTIVATEAPP :
  20245.            Debug( 3, "WM_ACTIVATEAPP: %s",
  20246.            (wParam)?"activate":"inactivate" );
  20247.            break;
  20248.       case WM_PAINT :
  20249.            Debug( 4, "WM_PAINT:" );
  20250.            break;
  20251.  
  20252.       default :
  20253.            break;
  20254.       }
  20255.  #endif
  20256.  
  20257.       /* process each message */
  20258.       switch( wMessage )
  20259.            {
  20260.       case WM_SYSCOMMAND : /* system command */
  20261.  
  20262.            /* In here you need to handle the special case where the
  20263.             * user asks for the debug control panel to be
  20264.             * displayed.
  20265.             * To do so you need to trap the control panel response
  20266.             * message you provided when installing the debug
  20267.             * utility.
  20268.             */
  20269.  
  20270.            /* process sub-message */
  20271.            switch( wParam )
  20272.                 {
  20273.  #if DEBUG
  20274.            case BUG_CONTROL : /* debug control panel */
  20275.                 DebugControl( hWnd );
  20276.                 break;
  20277.  #endif
  20278.            default :
  20279.                 lResult = DefWindowProc( hWnd, wMessage, wParam,
  20280.                 lParam );
  20281.                 break;
  20282.            }
  20283.  
  20284.            break;
  20285.       case WM_DESTROY :   /* destroy window */
  20286.            PostQuitMessage( 0 );
  20287.            break;
  20288.       default : /* send to default */
  20289.            lResult = DefWindowProc( hWnd, wMessage, wParam,
  20290.            lParam );
  20291.            break;
  20292.       }
  20293.  
  20294.       /* return normal result */
  20295.       return( lResult );
  20296.  
  20297.  }
  20298.  
  20299.  DEF File for BUGTEST
  20300.  
  20301.  NAME            BUGTEST
  20302.  
  20303.  DESCRIPTION    'Debug Test Utility'
  20304.  
  20305.  STUB           '\BIN\WINSTUB.EXE'
  20306.  
  20307.  CODE           MOVEABLE
  20308.  DATA           MOVEABLE MULTIPLE
  20309.  
  20310.  HEAPSIZE  4096
  20311.  STACKSIZE      4096
  20312.  
  20313.  EXPORTS
  20314.    TestWndFn    @1
  20315.    DebugControlDlgFn     @2
  20316.  
  20317.  Resource File for BUGTEST
  20318.  
  20319.  /*
  20320.   * DEBUG TEST PROGRAM - RESOURCE FILE
  20321.   *
  20322.   * LANGUAGE    : Microsoft C 5.0
  20323.   * MODEL       : small
  20324.   * STATUS      : operational
  20325.   *
  20326.   * 12/11/87 1.00 - Kevin P. Welch - initial creation.
  20327.   *
  20328.   */
  20329.  #include <style.h>
  20330.  #include "debug.h"
  20331.  
  20332.  BugTestIcon              ICON    bugtest.ico
  20333.  
  20334.  DebugControl DIALOG LOADONCALL MOVEABLE DISCARDABLE 8, 20, 185, 81
  20335.  CAPTION "Debug Control Panel"
  20336.  STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_POPUP
  20337.  BEGIN
  20338.   CONTROL "OFF - debug &inactive." BUG_OFF,"button",STD_RADIO,5,3,130,12
  20339.   CONTROL "ON - debug &active." BUG_ON,"button",STD_RADIO,5,15,128,12
  20340.   CONTROL "&Count debug events." BUG_COUNT,"button",STD_CHECKBOX,8,26,114,12
  20341.   CONTROL "&Display in debug window." BUG_DISPLAY,"button",STD_CHECKBOX,18,38,
  20342.   CONTROL "&Filter" BUG_FILTER,"button",STD_CHECKBOX,18,50,34,12
  20343.   CONTROL "" BUG_FILTERLIST,"edit",STD_EDITFIELD,55,50,78,12
  20344.   CONTROL "&Log to" BUG_LOG,"button",STD_CHECKBOX,18,62,36,12
  20345.   CONTROL "" BUG_LOGFILE,"edit",STD_EDITFIELD ,55,63,78,12
  20346.   CONTROL "" -1,"static",SS_FRAME | WS_CHILD,142,0,1,81
  20347.   CONTROL "Ok" BUG_OK,"button",DEF_BUTTON,148,4,32,14
  20348.   CONTROL "Cancel" BUG_CANCEL,"button",STD_BUTTON,148,21,32,14
  20349.  END
  20350.  
  20351.  ████████████████████████████████████████████████████████████████████████████
  20352.  
  20353.  An Examination of the Operating Principles of the Microsoft Object Linker
  20354.  
  20355.  Richard Wilton
  20356.  
  20357.  MS-DOS(R) object modules can be processed in two ways: they can be grouped
  20358.  together in object libraries or they can be linked into executable files.
  20359.  All Microsoft language translators are distributed with two utility
  20360.  programs that process object modules: the Microsoft Library Manager (LIB)
  20361.  creates and modifies object libraries and the Microsoft Object Linker
  20362.  (LINK) processes the individual object records within object modules to
  20363.  create executable files.
  20364.  
  20365.  The following discussion focuses on LINK because of its crucial role in
  20366.  creating an executable file.
  20367.  
  20368.  
  20369.  What LINK Does
  20370.  
  20371.  The function of LINK is to translate object modules into an executable
  20372.  program. LINK's input consists of one or more object files (OBJ files)
  20373.  and, optionally, one or more libraries (LIB files). LINK's output is an
  20374.  executable file (EXE file) containing binary data that can be loaded
  20375.  directly from the file into memory and executed. LINK can also generate a
  20376.  symbolic address map listing (MAP file)──a text file that describes the
  20377.  organization of the EXE file and the correspondence of symbols declared
  20378.  in the object modules to addresses in the executable file.
  20379.  
  20380.  
  20381.  Building an Executable
  20382.  
  20383.  LINK builds two types of information into an EXE file. First, it extracts
  20384.  executable code and data from the LEDATA and LIDATA records in object
  20385.  modules, arranges them in a specified order according to its rules for
  20386.  segment combination and relocation, and copies the result into the EXE
  20387.  file. Second, LINK builds a header for the EXE file. The header describes
  20388.  the size of the executable program and also contains a table of load-time
  20389.  segment relocations and initial values for certain CPU registers. See
  20390.  Pass 2 in the LINK Internals section. (See MSJ, Vol. 3 No. 2,
  20391.  "Exploring the Structure and Contents of the MS-DOS(R) Object Module
  20392.  Format," for more information on LEDATA, LIDATA, and other object
  20393.  records supported by LINK-Ed.)
  20394.  
  20395.  
  20396.  Relocation and Linking
  20397.  
  20398.  In building an executable image from object modules, LINK performs two
  20399.  essential tasks: relocation and linking. As it combines and rearranges the
  20400.  executable code and data it extracts from the object modules it
  20401.  processes, LINK frequently adjusts, or relocates, address references to
  20402.  account for the rearrangements (see Figure 1). LINK links object modules
  20403.  by resolving address references among them. It does this by matching the
  20404.  symbols declared in EXTDEF and PUBDEF object records (see Figure 2). LINK
  20405.  uses FIXUPP records in order to determine exactly how to compute both
  20406.  address relocations and linked address references.
  20407.  
  20408.  
  20409.  Object Module Order
  20410.  
  20411.  LINK processes input files from three sources: object files and libraries
  20412.  specified explicitly by the user (in the command line, in response to
  20413.  prompts by LINK, or in a response file) and object libraries named in
  20414.  object module COMENT records.
  20415.  
  20416.  LINK always uses all of the object modules in the object files it
  20417.  processes. In contrast, it extracts individual object modules from
  20418.  libraries-only those object modules needed to resolve references to public
  20419.  symbols are used. This difference is implicit in the order in which LINK
  20420.  reads its input files:
  20421.  
  20422.    ■  object files that are specified in the command line or in response to
  20423.       the Object Modules prompt
  20424.  
  20425.    ■  libraries that are specified in the command line or in response to
  20426.       the Libraries prompt
  20427.  
  20428.    ■  libraries that are specified in COMENT records
  20429.  
  20430.  The order in which LINK processes object modules influences the
  20431.  resulting executable file in three ways. First, the order in which
  20432.  segments appear in LINK's input files is reflected in the segment
  20433.  structure of the executable file. Second, the order in which LINK
  20434.  resolves external references to public symbols depends on the order in
  20435.  which it finds the public symbols in its input files. Finally, LINK
  20436.  derives the default name of the executable file from the name of the first
  20437.  input object file.
  20438.  
  20439.  
  20440.  Segment Order
  20441.  
  20442.  In general, LINK builds named segments into the executable file in the
  20443.  order in which it first encounters the SEGDEF records that declare the
  20444.  segments. (The /DOSSEG switch also affects segment order.) This means
  20445.  that the order in which segments appear in the executable file can be
  20446.  controlled by linking object modules in a specific order. In assembly
  20447.  language programs, it is best to declare all the segments used in the
  20448.  program in the first object module to be linked so that the segment order
  20449.  in the executable file is under complete control.
  20450.  
  20451.  
  20452.  Order in Which References Are Resolved
  20453.  
  20454.  LINK resolves external references in the order in which it encounters the
  20455.  corresponding public declarations. This fact is important as it determines
  20456.  the order in which LINK extracts object modules from libraries. When a
  20457.  public symbol required to resolve an external reference is declared more
  20458.  than once among the object modules in the input libraries, LINK uses the
  20459.  first object module that contains the public symbol. This means that the
  20460.  actual executable code or data that is associated with a particular
  20461.  external reference can be varied by changing the order in which LINK
  20462.  processes its input libraries.
  20463.  
  20464.  For example, imagine that a C programmer has written two versions of a
  20465.  function named myfunc() that is called by the program MYPROG.C. One
  20466.  version of myfunc() is for debugging; its object module is found in
  20467.  MYFUNC.OBJ. The other is a production version whose object module resides
  20468.  in MYLIB.LIB. Under ordinary circumstances, the programmer links the
  20469.  production version of myfunc() by using MYLIB.LIB (see Figure 3). In order
  20470.  to use the debugging version of myfunc(), the programmer explicitly
  20471.  includes its object module (MYFUNC.OBJ) when LINK is executed. This causes
  20472.  LINK to build the debugging version of myfunc() into the executable file
  20473.  because it finds the debugging version in MYFUNC.OBJ before it finds the
  20474.  other version in MYLIB.LIB.
  20475.  
  20476.  To exploit the order in which LINK resolves external references, it is
  20477.  important to know LINK's library search strategy: each individual library
  20478.  is searched repeatedly (from first library to last, in the sequence in
  20479.  which they are input to LINK) until no further external references can be
  20480.  resolved.
  20481.  
  20482.  The example in Figure 4 demonstrates this search strategy. Library
  20483.  LIB1.LIB contains object modules A and B, library LIB2.LIB contains object
  20484.  module C, and the object file MYPROG.OBJ contains the object module MAIN;
  20485.  modules MAIN, A, and C each contain an external reference to a symbol
  20486.  declared in another module. When this program is linked with
  20487.  
  20488.    LINK MYPROG,,,LIB1+LIB2
  20489.  
  20490.  LINK starts by incorporating the object module MAIN into the executable
  20491.  program. It then searches the input libraries until it resolves all the
  20492.  external references:
  20493.  
  20494.    ■  process MYPROG.OBJ, find unresolved external reference to A
  20495.    ■  search LIB1.LIB, extract A, find unresolved external reference to C
  20496.    ■  search LIB1.LIB again; reference to C remains unresolved
  20497.    ■  search LIB2.LIB, extract C, find unresolved external reference to B
  20498.    ■  search LIB2.LIB again; reference to B remains unresolved
  20499.    ■  search LIB1.LIB again, extract B
  20500.    ■  no more unresolved external references, so end library search
  20501.  
  20502.  The order in which the modules appear in the executable file thus
  20503.  reflects the order in which LINK resolves the external references; this,
  20504.  in turn, depends on which modules were contained in the libraries and on
  20505.  the order in which the libraries are input to LINK.
  20506.  
  20507.  
  20508.  Name of the Executable
  20509.  
  20510.  If no filename is specified in the command line or in response to the Run
  20511.  File prompt, LINK derives the name of the executable file from the name
  20512.  of the first object file it processes. For example, if the object files
  20513.  PROG1.OBJ and PROG2.OBJ are linked with the command
  20514.  
  20515.    LINK  PROG1+PROG2;
  20516.  
  20517.  the resulting executable file, PROG1.EXE, takes its name from the first
  20518.  object file processed by LINK.
  20519.  
  20520.  
  20521.  Order and Combinations
  20522.  
  20523.  LINK builds segments into the executable file by applying the following
  20524.  sequence of rules:
  20525.  
  20526.  Segments appear in the executable file in the order in which their
  20527.  SEGDEF declarations first appear in the input object modules.
  20528.  
  20529.  Segments in different object modules are combined if they have the same
  20530.  name and class and a public, memory, stack, or common combine type. All
  20531.  address references within the combined segments are relocated relative to
  20532.  the start of the combined segment.
  20533.  
  20534.    ■  Segments with the same name and either the public or the memory
  20535.       combine type are combined in the order in which they are processed by
  20536.       LINK. The size of the resulting segment then equals the total size
  20537.       of the combined segments.
  20538.  
  20539.    ■  Segments with the same name and the stack combine type are overlapped
  20540.       so that the data in each of the overlapped segments ends at the same
  20541.       address. The size of the resulting segment equals the total size of
  20542.       the combined segments. The resulting segment is always paragraph
  20543.       aligned.
  20544.  
  20545.    ■  Segments with the same name and the common combine type are
  20546.       overlapped so that the data in each of the overlapped segments starts
  20547.       at the same address. The size of the resulting segment equals the
  20548.       size of the largest of the overlapped segments.
  20549.  
  20550.  Segments with the same class name are concatenated.
  20551.  
  20552.  If the /DOSSEG switch is used, the segments are rearranged in conjunction
  20553.  with DGROUP.
  20554.  
  20555.  These rules allow the programmer to control the organization of
  20556.  segments in the executable file by ordering SEGMENT declarations in an
  20557.  assembly language source module, which produces the same order of SEGDEF
  20558.  records in the corresponding object module, and by placing this object
  20559.  module first in the order in which LINK processes its input files.
  20560.  
  20561.  A typical MS-DOS program is constructed by declaring all executable code
  20562.  and data segments with the public combine type, thus enabling the
  20563.  programmer to compile the program's source code from separate source
  20564.  code modules into separate object modules. When these object modules are
  20565.  linked, LINK combines the segments from the object modules according to
  20566.  the above rules to create logically unified code and data segments in the
  20567.  executable file.
  20568.  
  20569.  
  20570.  Segment Classes
  20571.  
  20572.  LINK concatenates segments with the same class name after it combines
  20573.  segments with the same segment name and class. For example, Figure 5 shows
  20574.  the following compiling and linking:
  20575.  
  20576.    MASM MYPROG1;
  20577.    MASM MYPROG2;
  20578.    LINK MYPROG1+MYPROG2;
  20579.  
  20580.  After MYPROG1.ASM and MYPROG2.ASM have been compiled, LINK builds the
  20581.  _TEXT and FAR_TEXT segments by combining segments with the same name
  20582.  from the different object modules. Then, _TEXT and FAR_TEXT are
  20583.  concatenated because they have the same class name ('CODE'). _TEXT is
  20584.  before FAR_TEXT in the executable file because LINK encounters the
  20585.  SEGDEF record for _TEXT before it finds the SEGDEF record for FAR_TEXT.
  20586.  
  20587.  
  20588.  Segment Alignment
  20589.  
  20590.  LINK aligns the starting address of each segment it processes according
  20591.  to the alignment specified in each SEGDEF record. It adjusts the
  20592.  alignment of each segment it encounters regardless of how that segment is
  20593.  combined with other segments of the same name or class. (The one
  20594.  exception is stack segments, which always start on a paragraph
  20595.  boundary.)
  20596.  
  20597.  Segment alignment is particularly important when public segments with
  20598.  the same name and class are combined from different object modules. Note
  20599.  what happens in Figure 6, where the three concatenated _DATA segments
  20600.  have different alignments. To enforce the word alignment and paragraph
  20601.  alignment of the _DATA segments in Module2 and Module3, LINK inserts one
  20602.  or more bytes of padding between the segments.
  20603.  
  20604.  
  20605.  Segment Groups
  20606.  
  20607.  A segment group establishes a logical segment address to which all offsets
  20608.  in a group of segments can refer, that is, all addresses in all segments
  20609.  in the group can be expressed as offsets relative to the segment value
  20610.  associated with the group (see Figure 7). Declaring segments in a group
  20611.  does not affect their positions in the executable file; the segments in
  20612.  a group may or may not be contiguous and can appear in any order as long
  20613.  as all address references to the group fall within 64Kb of each other.
  20614.  
  20615.  LINK reserves one group name, DGROUP, for use by Microsoft language
  20616.  translators. DGROUP is used to group compiler-generated data segments and
  20617.  a default stack segment.
  20618.  
  20619.  
  20620.  LINK Internals
  20621.  
  20622.  Many programmers use LINK as a "black box" program that transforms object
  20623.  modules into executable files. Nevertheless, it is helpful to observe
  20624.  how LINK processes object records to accomplish this task.
  20625.  
  20626.  LINK is a two-pass linker; that is, it reads all its input object modules
  20627.  twice. On Pass 1, LINK builds an address map of the segments and symbols
  20628.  in the object modules. On Pass 2, it extracts the executable code and
  20629.  program data from the object modules and builds a memory image-an exact
  20630.  replica-of the executable file.
  20631.  
  20632.  The reason LINK builds an image of the executable file in memory, instead
  20633.  of just copying code and data from object modules into the executable
  20634.  file, is that it organizes the executable file by segments and not
  20635.  according to the order in which it processes object modules. The most
  20636.  efficient way to concatenate, combine, and relocate the code and data is
  20637.  to build a map of the executable file in memory during Pass 1 and then
  20638.  fill in the map with code and data during Pass 2.
  20639.  
  20640.  In Versions 3.52 and later, whenever the /I (/INFORMATION) switch is
  20641.  specified in the command line, LINK displays status messages at the start
  20642.  of each pass and as it processes each object module. If the /M (that is,
  20643.  /MAP) switch is used in addition to the /I switch, LINK also displays the
  20644.  total length of each segment declared in the object modules. This
  20645.  information is helpful in determining how the structure of an
  20646.  executable file corresponds to the contents of the object modules
  20647.  processed by LINK.
  20648.  
  20649.  
  20650.  Pass 1
  20651.  
  20652.  During Pass 1, LINK processes the LNAMES, SEGDEF, GRPDEF, COMDEF, EXTDEF,
  20653.  and PUBDEF records in each input object module and uses the information
  20654.  in these object records to construct a symbol table and an address map of
  20655.  segments and segment groups.
  20656.  
  20657.  
  20658.  Symbol Table
  20659.  
  20660.  As each object module is processed, LINK uses the symbol table to resolve
  20661.  external references (declared in EXTDEF and COMDEF records) to public
  20662.  symbols. If LINK processes all the object files without resolving all the
  20663.  external references in the symbol table, it searches the input libraries
  20664.  for public symbols that match the unresolved external references. LINK
  20665.  continues to search each library until all the external references in the
  20666.  symbol table are resolved.
  20667.  
  20668.  
  20669.  Segments and Groups
  20670.  
  20671.  LINK processes every SEGDEF record according to the segment name, class
  20672.  name, and attributes specified in the record. LINK constructs a table of
  20673.  named segments and updates it as it concatenates or combines segments.
  20674.  This allows LINK to associate each public symbol in the symbol table with
  20675.  an offset into the segment in which the symbol is declared.
  20676.  
  20677.  LINK also generates default segments into which it places communal
  20678.  variables declared in COMDEF records. Near communal variables are placed
  20679.  in one paragraph-aligned public segment named c_common, with class name
  20680.  BSS (block storage space) and group DGROUP. Far communal variables are
  20681.  placed in a paragraph-aligned segment named FAR_BSS, with class name
  20682.  FAR_BSS. The combine type of each far communal variable's FAR_BSS segment
  20683.  is private (that is, not public, memory, common, or stack). As many
  20684.  FAR_BSS segments as necessary are generated.
  20685.  
  20686.  After all the object files have been read and all the external references
  20687.  in the symbol table have been resolved, LINK has a complete map of the
  20688.  addresses of all segments and symbols in the program. If a MAP file has
  20689.  been requested, LINK creates the file and writes the address map to it.
  20690.  Then LINK initiates Pass 2.
  20691.  
  20692.  
  20693.  Pass 2
  20694.  
  20695.  In Pass 2, LINK extracts executable code and program data from the LEDATA
  20696.  and LIDATA records in the object modules. It builds the code and data into
  20697.  a memory image of the executable file. During Pass 2, LINK also carries
  20698.  out all the address relocations and fixups related to segment relocation,
  20699.  segment grouping, and resolution of external references, as well as any
  20700.  other address fixups specified explicitly in object module FIXUPP records.
  20701.  
  20702.  If it determines during Pass 2 that not enough RAM is available to
  20703.  contain the entire image, LINK creates a temporary file in the current
  20704.  directory on the default disk drive. (LINK Version 3.60 and later use the
  20705.  environment variable TMP to find the directory for the temporary scratch
  20706.  file.) LINK then uses this file in addition to all the available RAM to
  20707.  construct the image of the executable file. (In versions of MS-DOS earlier
  20708.  than 3.0, the temporary file is named VM.TMP; in Version 3.0 and later,
  20709.  LINK uses Interrupt 21H Function 5AH to create the file.)
  20710.  
  20711.  LINK reads each of the input object modules in the same order as it did in
  20712.  Pass 1. This time it copies the information from each object module's
  20713.  LEDATA and LIDATA records into the memory image of each segment in the
  20714.  proper sequence. This is when LINK expands the iterated data in each
  20715.  LIDATA record it processes.
  20716.  
  20717.  LINK processes each LEDATA and LIDATA record along with the corresponding
  20718.  FIXUPP record, if one exists. LINK processes the FIXUPP record, performs
  20719.  the address calculations necessary for relocation, segment grouping, and
  20720.  resolving external references, and then stores binary data from the
  20721.  LEDATA or LIDATA record, including the results of the address
  20722.  calculations, in the proper segment in the memory image.
  20723.  
  20724.  The only exception to this process occurs when a FIXUPP record refers to
  20725.  a segment address. In this case, LINK adds the address of the fixup to a
  20726.  table of segment fixups; this table is used later to generate the segment
  20727.  relocation table in the EXE header.
  20728.  
  20729.  When all the data has been extracted from the object modules and all the
  20730.  fixups have been carried out, the memory image is complete. LINK now has
  20731.  all the information it needs to build the EXE header (see Figure 8). At
  20732.  this point, therefore, LINK creates the executable file and writes the
  20733.  header and all segments into it.
  20734.  
  20735.  
  20736.  Summary
  20737.  
  20738.  By using LINK to rearrange and combine segments, a programmer can
  20739.  generate an executable file in which segment order and addressing serve
  20740.  specific purposes. Careful use of LINK leads to more efficient use of
  20741.  memory and simpler, more efficient programs.
  20742.  
  20743.  LINK's characteristic support for segment ordering, for run-time memory
  20744.  management, and for dynamic overlays has an impact in many different
  20745.  situations. Programmers who write their own language translators must
  20746.  bear in mind the special conventions followed by LINK in support of
  20747.  Microsoft language translators. Application programmers must be
  20748.  familiar with LINK's capabilities when they use assembly language or
  20749.  link assembly language programs with object modules generated by
  20750.  Microsoft compilers. LINK is a powerful program development tool and
  20751.  understanding its special capabilities can lead to more efficient
  20752.  programs.
  20753.  
  20754.  
  20755.  Figure 1:  A simple relocation. Both object modules contain code that LINK
  20756.             combines into one logical segment. In this example, LINK appends
  20757.             the 50H bytes of code in Module2 to the 64H bytes of code in
  20758.             Module1. LINK relocates all references to addresses in the code
  20759.             segment so that they apply to the combined segment.
  20760.  
  20761.           ╔════════════════════╗          ╔════════════════════╗
  20762.           ║    Code segment    ║          ║                    ║
  20763.           ║     (64H bytes)    ║          ║    Code segment    ║
  20764.           ║                    ║          ║     (50H bytes)    ║
  20765.           ║Label1 at offset 10H║          ║                    ║
  20766.           ╚════════════════════╝          ╚════════════════════╝
  20767.                 Module 1                         Module 2
  20768.                           ╔══════════════════════╗
  20769.                           ║    Code segment      ║
  20770.                           ║    (B4H bytes)       ║
  20771.                           ║                      ║
  20772.                           ║ Label1 at offset 10H ║
  20773.                           ║ Label2 at offset 74H ║
  20774.                           ║                      ║
  20775.                           ╚══════════════════════╝
  20776.                            Combined code segment
  20777.  
  20778.  
  20779.  Figure 2:  Resolving an external reference. LINK resolves the external
  20780.             reference in Module1 (declared in an EXTDEF record) with the
  20781.             address of Label2 in Module2 (declared in a PUBDEF record).
  20782.  
  20783.           Module 1                          Module 2
  20784.         ╔══════════════════════════╗     ╔══════════════════════════╗
  20785.         ║ Code segment             ║     ║ Code segment             ║
  20786.         ║    EXTDEF Label2         ║     ║    PUBDEF Label2         ║
  20787.         ║       jmp Label2         ║     ║                          ║
  20788.         ║            ∙             ║     ║    Label2: ∙             ║
  20789.         ║            ∙             ║     ║            ∙             ║
  20790.         ║            ∙             ║     ║            ∙             ║
  20791.         ╚══════════════════════════╝     ╚══════════════════════════╝
  20792.                          Combined code segment
  20793.                        ╔═════════════════════════╗
  20794.                        ║ Code segment            ║
  20795.                        ║          ∙              ║
  20796.                        ║          ∙              ║
  20797.                        ║          ∙              ║
  20798.                        ║                         ║
  20799.                        ║   jmp Label2            ║
  20800.                        ║          ∙              ║
  20801.                        ║          ∙              ║
  20802.                        ║          ∙              ║
  20803.                        ║                         ║
  20804.                        ║ Label2:  ∙              ║
  20805.                        ║          ∙              ║
  20806.                        ║          ∙              ║
  20807.                        ╚═════════════════════════╝
  20808.  
  20809.  
  20810.  Figure 3:  Ordered object module processing by LINK. (a) With the command
  20811.             LINK MYPROG,,,MYLIB, the production version of myfunc() in
  20812.             MYLIB.LIB is used. (b) With the command LINK MYPROG+MYFUNC,,,
  20813.             MYLIB, the debugging bersion of myfunc() in MYFUNC.OBJ is used.
  20814.  
  20815.    ┌────────────────┐     ┌─────────────────────┐
  20816.    │ Main ()        │     │                     │
  20817.    │ {              ├────│ EXTDEF for myfunc() ├──┐   ┌───────────────┐
  20818.    │  x=myfunc(y) ; │     │                     │  │   │  Executable   │
  20819.    │ }              │     └─────────────────────┘  ├──│ code contains │
  20820.    └────────────────┘           MYPROG.OBJ         │   │   myfunc()    │
  20821.        MYPROG.C                                    │   │ derived from  │
  20822.                                                    │   │    either     │
  20823.    ┌────────────────┐     ┌─────────────────────┐  │   │ MUFUNC.OBJ or │
  20824.    │ myfunc(a)      │     │                     │  │   │   MYLIB.OBJ   │
  20825.    │ int a;         ├────│ PUBDEF for myfunc() ├──┤   └───────────────┘
  20826.    │ {              │     │                     │  │
  20827.    │     ∙          │     └─────────────────────┘  │
  20828.    │     ∙          │           MYFUNC.OBJ         │
  20829.    │     ∙          │                              │
  20830.    │ }              │     ┌─────────────────────┐  │
  20831.    └────────────────┘     │             ∙       │  │
  20832.        MYFUNC.C           │             ∙       │  │
  20833.                           │             ∙       │  │
  20834.                           │ PUBDEF for myfunc() ├──┘
  20835.                           │             ∙       │
  20836.                           │             ∙       │
  20837.                           │             ∙       │
  20838.                           └─────────────────────┘
  20839.                                 MYLIB.LIB
  20840.  
  20841.  
  20842.  Figure 4:  Library search order. Modules are incorporated into the
  20843.             executable file as LINK extracts them from the libraries to
  20844.             resolve external references.
  20845.  
  20846.                                                        Start of program─┐
  20847.  ┌─────────────┐  ┌─────────────┐  ┌────────────────┐  ┌──────────────┐┐│
  20848.  │   ModuleA   │  │   ModuleC   │  │   ModuleMAIN   │  │  ModuleMAIN  │├┘
  20849.  │   Call C    │  │   Call B    │  │   Call C       │  ├──────────────┤┘
  20850.  ├─────────────┤  └─────────────┘  └────────────────┘  │  ModuleA     │
  20851.  │   ModuleB   │     LIB2.LIB          MYPROG.OBJ      ├──────────────┤
  20852.  │             │                                       │  ModuleC     │
  20853.  └─────────────┘                                       ├──────────────┤
  20854.     LIB1.LIB                                           │  ModuleB     │
  20855.                                                        └──────────────┘
  20856.                                                           MYPROG.EXE
  20857.  
  20858.  
  20859.  Figure 5:  Segment order and concatenation by LINK. The start of each file,
  20860.             corresponding to the lowest address, is at the top.
  20861.  
  20862.        MYPROG1.ASM
  20863.       ┌──────────────────────────────┐   MYPROG1.OBJ
  20864.       │ _TEXT SEGMENT public 'CODE'  │  ┌───────────────────┐
  20865.       ├──────────────────────────────┤  │SEGDEF for_TEXT    │
  20866.       │FAR_TEXT SEGMENT public 'CODE'├─│SEGDEF for FAR_TEXT├───┐
  20867.       ├──────────────────────────────┤  │SEFDEF for_DATA    │   │
  20868.       │ _DATA SEGMENT public 'DATA'  │  └───────────────────┘   │
  20869.       └──────────────────────────────┘                          │
  20870.                                                                 │
  20871.        MYPROG2.ASM                                              │
  20872.       ┌──────────────────────────────┐   MYPROG2.OBJ            │
  20873.       │ _TEXT SEGMENT public 'CODE'  │  ┌───────────────────┐   │
  20874.       ├──────────────────────────────┼─│SEGDEF for_TEXT    │   │
  20875.       │FAR_TEXT SEGMENT public 'CODE'│  │SEGDEF for FAR_TEXT├───┤
  20876.       └──────────────────────────────┘  └───────────────────┘   │
  20877.           ┌─────────────────────────────────────────────────────┘
  20878.           │    MYPROG1.EXE
  20879.           │   ┌────────────┐─┐           ─┐
  20880.           │   │            │ ├─ _TEXT     │
  20881.           │   │            │ │  segment   │
  20882.           └──│            │ │            │
  20883.               ├────────────┤═╡            ├─ 'CODE' class
  20884.               │            │ │            │
  20885.               │            │ ├─ FAR_TEXT  │
  20886.               │            │ │  segment   │
  20887.               │            │ │            │
  20888.               ├────────────┤═╡           ─┘
  20889.               │            │ ├─ _DATA
  20890.               └────────────┘─┘  segment
  20891.  
  20892.  
  20893.  Figure 6:  Alignment of combined segments. LINK enforces segment alignment by
  20894.             padding combined segments with unitialized data bytes.
  20895.  
  20896.           Module 1              Module 2                Module 3
  20897.   ╔═══════════════════╗  ╔════════════════════╗  ╔════════════════════╗
  20898.   ║ DATA SEGMENT byte ║  ║ DATA SEGMENTS word ║  ║ DATA SEGMENTS para ║
  20899.   ║ public 35H bytes  ║  ║ public 35H bytes   ║  ║ public 35H bytes   ║
  20900.   ╚═══════════════════╝  ╚════════════════════╝  ╚════════════════════╝
  20901.  
  20902.      00H┌─────────────────────────────┐─┐
  20903.         │         Module 1            │ ├─35H bytes (byte aligned)
  20904.      35H├─────────────────────────────┤─┘
  20905.         │█████████████████████████████│
  20906.      36H├─────────────────────────────┤─┐
  20907.         │         Module 2            │ ├─35H bytes (word aligned)
  20908.      6BH├─────────────────────────────┤─┘
  20909.         │█████████████████████████████│
  20910.         │█████████████████████████████│
  20911.      70H├─────────────────────────────┤─┐
  20912.         │         Module 3            │ ├─35H bytes (paragraph aligned)
  20913.         └─────────────────────────────┘─┘
  20914.              Resulting _DATA segment
  20915.                 in .EXE file
  20916.  
  20917.  
  20918.  Figure 7:  Example of group addressing. The first MOV loads the value 00H
  20919.             into AX (the offset of TestData relative to DataSeg2); the second
  20920.             MOV loads the value 100H into AX (the offset of TestData relative
  20921.             to the group DataGroup).
  20922.  
  20923.  DataGroup GROUP     DataSeg1,DataSeg2
  20924.  CodeSeg   SEGMENT   byte public 'CODE'
  20925.            ASSUME    cs:CodeSeg
  20926.  
  20927.            mov       ax,offset DataSeg2:TestData
  20928.            mov       ax,offset DataGroup:TestData
  20929.  
  20930.  CodeSeg   ENDS
  20931.  
  20932.  DataSeg1  SEGMENT   para public 'DATA'
  20933.            DB        100h dup(?)
  20934.  DataSeg1  ENDS
  20935.  
  20936.  DataSeg2  SEGMENT   para public 'DATA'
  20937.  TestData  DB        ?
  20938.  DataSeg2  ENDS
  20939.            END
  20940.  
  20941.  
  20942.  Figure 8:  How LINK Builds an EXE File Header
  20943.  
  20944. ╓┌───────┌──────────────────────────────────┌────────────────────────────────
  20945.  Offset  Contents                           Comments
  20946.  
  20947.  00H     'MZ'                               EXE file signature
  20948.  
  20949.  02H     Length of executable              ┐
  20950.          image MOD 512                     │
  20951.                                            │  Total size of all segments
  20952.  04H     Length of executable image in     │  plus EXE file header
  20953.          512-byte pages, including last    │
  20954.          partial page (if any)             ┘
  20955.  
  20956.  06H     Number of run-time                 Number of segment fixups
  20957.          segment relocations
  20958.  
  20959.  08H     Size of the EXE header in          Size of segment relocation table
  20960.  Offset  Contents                           Comments
  20961. 08H     Size of the EXE header in          Size of segment relocation table
  20962.          16-byte paragraphs
  20963.  
  20964.  0AH     MINALLOC: Minimum amount           Size of uninitialized data and/or
  20965.          of RAM to be allocated above       stack segments at end of program
  20966.          end of the loaded program (in      (0 if /HI switch is used)
  20967.          16-byte paragraphs)
  20968.  
  20969.  0CH     MAXALLOC: Maximum amount           0 if /HI switch is used; value
  20970.          of RAM to be allocated above       specified with /CP switch;
  20971.          end of the loaded program (in      FFFFH if /CP and /HI switches
  20972.          16-byte paragraphs)                are not used
  20973.  
  20974.  0EH     Stack segment (initial value for   Address of stack segment relative
  20975.          SS register); relocated by         to start of executable image
  20976.          MS-DOS when program is loaded
  20977.  
  20978.  10H     Stack pointer (initial             Size of stack segment in bytes
  20979.          value for register SP)
  20980.  
  20981.  Offset  Contents                           Comments
  20982. 
  20983.  12H     Checksum                           One's complement of sum of all
  20984.                                             words in file, excluding
  20985.                                             checksum itself
  20986.  
  20987.  14H     Entry point offset (initial       ┐
  20988.          value for register IP)            │
  20989.                                            │  MODEND object record that
  20990.  16H     Entry point segment (initial      │  specifies program start address
  20991.          value for register CS);           │
  20992.          relocated by MS-DOS when          │
  20993.          program is loaded                 ┘
  20994.  
  20995.  18H     Offset of start of segment
  20996.          relocation table relative
  20997.          to start of EXE header
  20998.  
  20999.  1AH     Overlay number                     0 for resident segments;
  21000.                                             >0 for overlay segments
  21001.  
  21002.  Offset  Contents                           Comments
  21003. 
  21004.  1CH     Reserved
  21005.  
  21006.  
  21007.  
  21008.  ════════════════════════════════════════════════════════════════════════════
  21009.  
  21010.  
  21011.  Vol. 3 No. 4 Table of Contents
  21012.  
  21013.  DARWIN: Merrill Lynch Develops a New Workstation Based on Windows 2.0
  21014.  
  21015.  In order to easily access data from numerous incompatible databases and
  21016.  provide a graphical interface with report writing capabilities, Merrill
  21017.  Lynch turned to the Windows development environment and created DARWIN
  21018.  (Data Access and Reporting Through Windows).
  21019.  
  21020.  
  21021.  CodeView(R) for Windows Provides an Interactive Debugging Environment
  21022.  
  21023.  An interactive debugger allows immediate interaction with an executing
  21024.  program. CodeView for Windows (CVW) is a special version of the Microsoft
  21025.  debugger that understands such Windows specific concepts as messages, the
  21026.  Windows Memory Manager and dynamic-link libraries.
  21027.  
  21028.  
  21029.  OS/2 Graphics Programming Interface: An Introduction to Coordinate Spaces
  21030.  
  21031.  Coordinate spaces, which define drawing areas, are an important component of
  21032.  the OS/2 Presentation Manager. Transforms allow the mapping of objects from
  21033.  one coordinate space to the next. Coordinate spaces under the micro-PS and
  21034.  some of the GPI functions for handling transforms are explored.
  21035.  
  21036.  
  21037.  Microsoft(R) Macro Assembler Version 5.1 Simplifies Macros and Interfacing
  21038.  
  21039.  Macro Assembler 5.1 provides significant new features that enable the
  21040.  programmer to easily develop macros and interface to high-level languages.
  21041.  Other new features include the addition of high-level constructs such as
  21042.  ELSE extensions, local labels, and type directives.
  21043.  
  21044.  
  21045.  Color Mixing Principles and How Color Works in the Raster Video Model
  21046.  
  21047.  The underlying principles of color generation are complex. Exploring some
  21048.  common color mixing models such as the RGB (Red-Green-Blue) and HSV
  21049.  (Hue-Saturation-Value) models leads to a better understanding of the methods
  21050.  employed for creating colors on PC video displays.
  21051.  
  21052.  
  21053.  Creating User-Defined Controls for Your Own Windows Applications
  21054.  
  21055.  The ability to define your own controls functionally extends the Windows
  21056.  user interface, giving your applications greater visual appeal and the user
  21057.  greater program control. Palette, a color mixing program, utilizes several
  21058.  such controls, as provided in a program called Spectrum.
  21059.  
  21060.  
  21061.  SQL Server Brings Distributed DBMS Technology to OS/2 Via LAN Manager
  21062.  
  21063.  SQL Server, in combination with the OS/2 LAN Manager, brings mainframe
  21064.  and minicomputer power to networked personal computers. It adds stored
  21065.  procedures, triggers, and an advanced transaction-oriented kernel,
  21066.  essential features for assuring data integrity, to SQL.
  21067.  
  21068.  
  21069.  Ask Dr. Bob
  21070.  
  21071.  
  21072.  EDITOR'S NOTE
  21073.  
  21074.  What does OS/2 mean for the professional programmer? For one thing, it means
  21075.  new ways of thinking about how programs are developed. By itself,
  21076.  multitasking is a boon to programmers; it's a real time-saver to have an
  21077.  editor, compiler, and debugger all running concurrently.
  21078.  
  21079.  Debugging under OS/2 with the Presentation Manager (PM) offers some new
  21080.  alternatives that just weren't feasible under DOS. In the last issue we
  21081.  presented a debugging utility for Windows applications consisting of about
  21082.  600 lines of code. Building an equivalent debugging tool under Presentation
  21083.  Manager is a trivial task.
  21084.  
  21085.  Let's assume that you have at your disposal a UNIX(R)-like grep function
  21086.  that will locate whatever strings you might be looking for. Within your PM
  21087.  application, whenever you want to generate debugging information you simply
  21088.  include a printf statement, for example:
  21089.  
  21090.    printf('17 BeginPaint returned 0x%04x\n', usResult);
  21091.  
  21092.  You would then start your PM application from a windowed instance of
  21093.  CMD.EXE:
  21094.  
  21095.    start myPMapp | grep '.*'
  21096.  
  21097.  which would bring up the PM application in a window, while the printf output
  21098.  ends up being piped to the instance of grep running in the CMD.EXE window.
  21099.  With grep, you could, for example, filter out everything but type 17
  21100.  messages simply by starting the PM application as follows:
  21101.  
  21102.    start myPMapp | grep '^17'
  21103.  
  21104.  The appropriate messages would then be filtered out. Of course, you would
  21105.  need a much more elaborate OS/2 VIO-based filter for significantly greater
  21106.  flexibility.
  21107.  
  21108.  The multitasking/windowing facilities available under OS/2 and Presentation
  21109.  Manager present programmers with a far richer set of options, in many cases
  21110.  making formerly complex tasks much simpler. The successful OS/2 programmer
  21111.  will be the one with the vision to put these new advantages to work.──Ed.
  21112.  
  21113.  Masthead
  21114.  
  21115.  JONATHAN D. LAZARUS
  21116.  Editor and Publisher
  21117.  
  21118.  EDITORIAL
  21119.  
  21120.  TONY RIZZO
  21121.  Technical Editor
  21122.  
  21123.  KAREN STRAUSS
  21124.  Assistant Editor
  21125.  
  21126.  JOANNE STEINHART
  21127.  Production Editor
  21128.  
  21129.  GERALD CARNEY
  21130.  Staff Editor
  21131.  
  21132.  KIM HOROWITZ
  21133.  Editorial Assistant
  21134.  
  21135.  ART
  21136.  
  21137.  MICHAEL LONGACRE
  21138.  Art Director
  21139.  
  21140.  VALERIE MYERS
  21141.  Associate Art Director
  21142.  
  21143.  CIRCULATION
  21144.  
  21145.  WILLIAM B. GRANBERG
  21146.  Circulation Manager
  21147.  
  21148.  L. PERRIN TOMICH
  21149.  Assistant to the Publisher
  21150.  
  21151.  DONNA PUIZINA
  21152.  Administrative Assistant
  21153.  
  21154.  Copyright(C) 1988 Microsoft Corporation. All rights reserved; reproduction
  21155.  in part or in whole without permission is prohibited.
  21156.  
  21157.  Microsoft Systems Journal is a publication of Microsoft Corporation, 16011
  21158.  NE 36th Way, Box 97017, Redmond, WA 98073-9717. Officers: William H.
  21159.  Gates, III, Chairman of the Board and Chief Executive Officer; Jon Shirley,
  21160.  President and Chief Operating Officer; Francis J. Gaudette, Treasurer;
  21161.  William Neukom, Secretary.
  21162.  
  21163.  Microsoft Corporation assumes no liability for any damages resulting from
  21164.  the use of the information contained herein.
  21165.  
  21166.  Microsoft, MS, and CodeView are registered trademarks of Microsoft
  21167.  Corporation. CompuServe is a registered trademark of CompuServe, Inc. dBASE,
  21168.  dBASE III, and dBASE III PLUS are registered trademarks of Ashton-Tate
  21169.  Corporation. EtherNet is a registered trademark of Xerox Corporation. IBM is
  21170.  a registered trademark of International Business Machines Corporation.
  21171.  Token-Ring is a trademark of International Business Machines Corporation.
  21172.  Intel is a registered trademark of Intel Corporation. Above is a trademark
  21173.  of Intel Corporation. AST and RAMpage! are registered trademarks of AST
  21174.  Research, Inc. SQLBase is a registered trademark of Gupta Technologies, Inc.
  21175.  UNIX is a registered trademark of American Telephone and Telegraph
  21176.  Company.
  21177.  
  21178.  ████████████████████████████████████████████████████████████████████████████
  21179.  
  21180.  DARWIN: Merrill Lynch Develops a New Workstation Based on Windows 2.03
  21181.  
  21182.  Tony Rizzo and Karen Strauss
  21183.  
  21184.  What do you do when you need information from a wide variety of
  21185.  databases, each with its own access method, and you want to ensure vendor
  21186.  independence in workstation hardware and applications software, but you
  21187.  also want to provide a common, graphics-oriented user interface with
  21188.  report-writing capabilities? You turn to the Windows development
  21189.  environment and write a snappy application that uses all of the tools
  21190.  that Windows puts at your disposal.
  21191.  
  21192.  Corporate MIS, a relatively new division of Merrill Lynch & Co., Inc., is
  21193.  responsible for providing headquarters management with financial control
  21194.  and human resources information. In the past, this information has resided
  21195.  in many separate database systems, each with its own access method,
  21196.  usually incompatible with the others. Because of the difficulties in
  21197.  combining them, about a year ago Corporate MIS began considering new
  21198.  ways of accessing data through personal computers.
  21199.  
  21200.  A key issue for Corporate MIS was the development of a strategy to allow
  21201.  different programming teams within the division to work independently
  21202.  of each other, yet adhere to common design specifications. For example,
  21203.  the database design group would work separately from the information
  21204.  presentation group, but each would develop applications to be
  21205.  compatible and consistent across all groups.
  21206.  
  21207.  Corporate MIS came up with a plan to base all future organization of its
  21208.  data on the relational model and use Structured Query Language (SQL) as
  21209.  the means of accessing the data. Its database designers would be free to
  21210.  select the relational technology best suited to any given task. The
  21211.  database engine could be based on a LAN, a mainframe, or a database
  21212.  machine──the only requirement would be that the technology had to be able
  21213.  to execute "standard" SQL queries.
  21214.  
  21215.  Workstation designers directly concerned with how data is presented on a
  21216.  PC to the end user would only have to deal with generating standard SQL
  21217.  requests for data and handling data returned to the workstation from the
  21218.  database engine.
  21219.  
  21220.  On the user side, the Corporate MIS plan required that workstation
  21221.  designers use a common interactive graphical interface. With a PC-based
  21222.  graphical interface a designer no longer had to be concerned with
  21223.  building applications that rely, for example, on asynch or 3270-emulation
  21224.  windows. Also, Corporate MIS wanted to provide users with a common
  21225.  report writer facility to simplify formatting data.
  21226.  
  21227.  This plan envisioned a common development tool that any workstation
  21228.  designer could incorporate into an application whenever that application
  21229.  had to access and format data. It was at this point that Corporate MIS
  21230.  turned to its PC Software Development group. "Our charter was to design
  21231.  a method for querying SQL databases using icons or pictures of what the
  21232.  user wanted to do," says Anthony Pizi, an assistant vice president within
  21233.  Corporate MIS responsible for PC software development.
  21234.  
  21235.  Pizi decided to use the Microsoft(R) Windows Version 2.03 environment as
  21236.  his tool's common graphical user interface; after all, Microsoft Excel,
  21237.  with its integrated, Windows-based interface, had been an instant success
  21238.  with Merrill Lynch analysts. "This was our first major development effort
  21239.  using Windows, so the project took on a subtheme demonstrating that
  21240.  Windows really could deliver the graphical interface and software
  21241.  integration that we needed," says Pizi. "And so far, so good."
  21242.  
  21243.  As the software begins to take shape, according to Pizi, Merrill Lynch is
  21244.  simultaneously reorganizing its PC hardware, under which most PCs will
  21245.  eventually be connected to various LAN systems, such as EtherNet(R) and
  21246.  Token-Ring(TM).
  21247.  
  21248.  
  21249.  Defining the System
  21250.  
  21251.  Pizi had to implement and integrate three specific functions into his
  21252.  design.
  21253.  
  21254.  First, a front-end system of menu options, dialog boxes, and pop-ups would
  21255.  let a user formulate complex requests by simply pointing and clicking on
  21256.  various fields and commands with a mouse. Second, the application had to
  21257.  be able to take requests formulated by the user, translate them into SQL
  21258.  requests, send them to the appropriate database subsystem, and handle
  21259.  the returned data. Finally, the application required the ability to
  21260.  format data easily in any manner.
  21261.  
  21262.  This set of tools would not be tied to any specific database. The original
  21263.  model might, for example, be based on Gupta Technologies' SQLBase(R), but
  21264.  any engine could later be integrated into the system.
  21265.  
  21266.  Furthermore, the entire application would be written to be NetBIOS-
  21267.  compatible, permitting it to run on any workstation attached to any LAN
  21268.  supporting NetBIOS. The workstation/LAN environment would be hooked into a
  21269.  SQL Network Gateway, providing links to such mainframe databases as
  21270.  Merrill Lynch's IBM(R) DB2 system.
  21271.  
  21272.  For end users the resulting applications and workstations would provide:
  21273.  
  21274.     ■  consistent hardware configurations
  21275.     ■  a consistent user interface
  21276.     ■  remote access to many corporate databases
  21277.     ■  a simple means of generating SQL queries
  21278.     ■  substantial report writer capabilities
  21279.  
  21280.  These new systems would improve Corporate MIS's ability to leverage
  21281.  existing systems and human resources, offering:
  21282.  
  21283.     ■  reduced costs
  21284.     ■  increased productivity
  21285.     ■  improved information flow
  21286.     ■  enhanced and more informed decision making
  21287.     ■  better control of the PC environment
  21288.  
  21289.  
  21290.  Building DARWIN
  21291.  
  21292.  Pizi's group started to experiment with the Windows development
  21293.  environment and to develop the strategies for communicating with
  21294.  remote back-end database systems. Once the group had demonstrated their
  21295.  ability to talk with a database through various front-end models they put
  21296.  together under Windows, Pizi felt they were ready to begin.
  21297.  
  21298.  The design for Data Access and Reporting Through Windows (DARWIN) was
  21299.  turned over to Steve Canny and Brian Hayden, two senior
  21300.  programmer/analysts. "On December 15 of last year the first line of
  21301.  DARWIN code was written," says Pizi. Five months later DARWIN was
  21302.  virtually complete.
  21303.  
  21304.  As Canny tells it, "In the beginning, building DARWIN was a classic
  21305.  example of design on the fly. Neither Brian [Hayden] nor I were Windows
  21306.  masters. We would see some portion of what needed to be done and go off
  21307.  and see if we could get Windows to do it. It was kind of like a jigsaw
  21308.  puzzle, where you find pieces one at a time. Occasionally two or three
  21309.  pieces would fall into place together.
  21310.  
  21311.  "One of us would come up with the basics of an idea, we'd discuss it, then
  21312.  implement it in the next version of the software. Since I had the most
  21313.  experience with graphics programming, I developed the user interface.
  21314.  Brian knew the database end of things and designed the link to the
  21315.  database server. Brian also developed the code for all of the DARWIN
  21316.  dialog boxes that present data to the user. Eventually, we were able to
  21317.  get the entire thing to compile, and the beta version of DARWIN was born."
  21318.  
  21319.  
  21320.  DARWIN and Microsoft Excel
  21321.  
  21322.  Originally, DARWIN was to be responsible for displaying and printing the
  21323.  query results. "We had managed to put together a low-level stub for
  21324.  handling the display and were sweating about the thought of writing a
  21325.  display and print manager that had reasonable features," Canny says.
  21326.  
  21327.  "Fortunately, it was just about then that Tony [Pizi] came in shaking his
  21328.  hands over his head and talking fast, the way he usually does when he's
  21329.  had a particularly good brainstorming session. He told us about his idea
  21330.  to send DARWIN's output directly to Microsoft Excel, and we told him he
  21331.  was crazy. Two days later we began to think that maybe it wasn't so crazy.
  21332.  Then we became absolutely convinced that sending the output directly to
  21333.  Microsoft Excel was the way to go," says Canny.
  21334.  
  21335.  A user goes into DARWIN, builds a query, and the data goes directly into a
  21336.  Microsoft Excel spreadsheet. This idea resulted in a substantial
  21337.  breakthrough, and the third tool for which DARWIN was responsible began to
  21338.  fall into place. The design changed so that Microsoft Excel became, in a
  21339.  sense, a part of DARWIN; the spreadsheet would serve as DARWIN's report
  21340.  writer (see Figure 1) and would, in fact, be responsible for bringing up
  21341.  DARWIN itself.
  21342.  
  21343.  
  21344.  Using DARWIN
  21345.  
  21346.  To bring up DARWIN, the user executes a Microsoft Excel macro, which
  21347.  supplies DARWIN with its menu bar and hides Microsoft Excel. Once DARWIN
  21348.  is running, the macro controls whether the user is in DARWIN itself or in
  21349.  Microsoft Excel by monitoring the appropriate menu option provided in
  21350.  the two applications. The user doesn't even have to know how to start
  21351.  Microsoft Excel or run a macro if a program such as the hDC Computer
  21352.  Corporation ClickStart applications organizer is used──simply clicking on
  21353.  an icon will start DARWIN.
  21354.  
  21355.  DARWIN establishes the underlying connections to the required database.
  21356.  The database can be a server on a network, or it can be a mainframe
  21357.  database that is linked to via a SQL Network Gateway (see Figure 2).
  21358.  Gupta's SQLBase supplies the necessary software to make this mainframe
  21359.  link look like just another local network-based server to DARWIN.
  21360.  
  21361.  DARWIN offers the user database choices. Tables of data are grouped
  21362.  together as categories; each category consists of preestablished
  21363.  collections and groupings of data tables. The user first selects the
  21364.  desired database category. Once a selection is made, DARWIN provides a
  21365.  list of the fields in each table and menus of tools for extracting the
  21366.  required data (see Figures 3, 4, and 5).
  21367.  
  21368.  DARWIN's client area, which is an iconic representation of the query
  21369.  being assembled, is divided into two child windows. The top window
  21370.  containing the short horizontal scroll bar is the Choice List window; the
  21371.  other is the Database Context window.
  21372.  
  21373.  The Choice List area corresponds to the SQL SELECT clause; it displays
  21374.  the columns that will make up the result table, that is, the information
  21375.  for which the database is being queried. The Database Context window
  21376.  corresponds to the FROM and WHERE clauses; it shows the tables from which
  21377.  the selected columns come and the constraints and joins that are detailed
  21378.  in the WHERE clause. [For more information on SQL see "SQL: A Short
  21379.  Primer" in this issue──Ed.]
  21380.  
  21381.  To the user of the DARWIN query tool, the tables of data are represented
  21382.  on the screen as boxes, and they consist of one or more columns represented
  21383.  on the screen as rows, not to be confused with rows of data. These boxes are
  21384.  simple child windows (windows without scroll bars, menu bars, and so on)
  21385.  that can be collapsed by clicking on the minimize arrow and dragged around
  21386.  the client area with a mouse.
  21387.  
  21388.  
  21389.  Creating Joins
  21390.  
  21391.  Joins are created by clicking the left mouse button on the first data
  21392.  item, then clicking the right mouse button on a second data item in
  21393.  another table. The left-right button combinations are analogous to setting
  21394.  up a "from (the left button) ... to (the right button)" relationship.
  21395.  After the right button is clicked DARWIN draws a line from one table to
  21396.  another (see Figure 6). The join that is formed by default is an equi-
  21397.  join, the most commonly used join.
  21398.  
  21399.  The type of join created is easily modified by the user (see Figure 7).
  21400.  Join constraints are established by clicking on an appropriate column and
  21401.  using the dialog box that is then presented. Several constraint options,
  21402.  such as the mathematical symbols for greater than, less than, less than
  21403.  or equal to, and so on, are offered. The user establishes data constraints
  21404.  through a similar process (see Figure 8).
  21405.  
  21406.  The user is thus able to formulate multitable queries based on joins
  21407.  made only through mouse manipulations and menu choices presented by
  21408.  DARWIN. With a visual summary of database tables displayed on screen,
  21409.  users can easily discover relationships between collections of data that
  21410.  they didn't realize they had access to. The user is provided with all the
  21411.  information and tools necessary to build a detailed query, based on
  21412.  standard Windows objects: windows, listboxes, and pull-down/pop-up
  21413.  menus. Figure 9 shows the resulting graphical representation of a join
  21414.  with a data constraint.
  21415.  
  21416.  DARWIN takes the user-supplied information, which is stored in a single
  21417.  dynamic local data structure, and creates a standard SQL query. Once the
  21418.  actual SQL statements are created, the user clicks on the RUN! command,
  21419.  and DARWIN does the rest, going out to the necessary tables, collecting
  21420.  the data, and writing it out to a CE_BIFF file, which is the Microsoft
  21421.  Excel file format.
  21422.  
  21423.  When DARWIN indicates that a query has been completed and a result table
  21424.  thus created, the user simply toggles back to Microsoft Excel by clicking
  21425.  on the View Output option under the Window pull-down menu (see Figure 10)
  21426.  and finds the data, supplied through the CE_BIFF file that has just been
  21427.  created, waiting in a standard Microsoft Excel spreadsheet (see Figure
  21428.  11). According to Pizi, because Microsoft Excel was designed with the
  21429.  capacity to handle database information, the spreadsheet already had some
  21430.  of the most important tools for becoming a report writer.
  21431.  
  21432.  Data can be modified and formatted and statistics generated by using all
  21433.  of Microsoft Excel's spreadsheet, formatting, and charting features. The
  21434.  user can even click on the A1 cell, and Microsoft Excel will return
  21435.  information related to the underlying SQL request made in DARWIN (see
  21436.  Figure 12).
  21437.  
  21438.  The user can also view any actual SQL query statement that is in the
  21439.  process of being built, and experienced users are even able to modify the
  21440.  query through a feature called SQL EDIT (see Figures 13 and 14). At this
  21441.  point, according to Pizi, more complex nested queries are not yet
  21442.  supported, but a knowledgeable user can build more complex requests
  21443.  through SQL EDIT.
  21444.  
  21445.  
  21446.  Tight Integration
  21447.  
  21448.  Microsoft Excel and DARWIN are very tightly integrated. "We like people
  21449.  to ask, 'Are you in DARWIN or Microsoft Excel?'" Hayden says. "It is so
  21450.  seamless, it is hard to tell where one ends and the other begins [see
  21451.  Figure 15]. Basically, when the table screens are up, you are in DARWIN.
  21452.  When you have the data you see in the report, you are in Microsoft Excel."
  21453.  
  21454.  Hayden credits Microsoft Excel for this now-you-see-it, now-you-don't
  21455.  quality. "Microsoft Excel has an incredibly rich program language and allows
  21456.  you to use Windows function calls via macros that make Windows environment
  21457.  calls. The macro we've developed in Microsoft Excel maximizes an
  21458.  application, in our case DARWIN, and adds DARWIN's menu bar," he says.
  21459.  
  21460.  "The command is 'minimize Excel, maximize DARWIN,' so DARWIN comes up on
  21461.  the screen. Microsoft Excel is the landing pad for the data DARWIN
  21462.  provides," says Hayden. When the user wants to view the data in Microsoft
  21463.  Excel, the process is reversed. Figure 16 shows an early version of the
  21464.  macro; a close inspection of the macro demonstrates how simple it is to go
  21465.  from the spreadsheet to DARWIN and back.
  21466.  
  21467.  
  21468.  Powerful Environment
  21469.  
  21470.  The power resulting from the integration of DARWIN and Microsoft Excel was
  21471.  not immediately obvious to Canny and Hayden. Canny says, "When we first
  21472.  explored Microsoft Excel as a possibility, it took a while to assimilate
  21473.  the technology. Over time, as we better understood the environment we
  21474.  realized what a really great opportunity there was for developing a unique
  21475.  application."
  21476.  
  21477.  For example, the format in which data returned by DARWIN would be
  21478.  represented required much thought, and various scenarios were drawn up.
  21479.  Canny says, "At first, there was a good deal of experimentation. We didn't
  21480.  have all the specs on Microsoft Excel and were waiting around for that.
  21481.  The CE_BIFF format was not our first choice; we arrived at it after trying
  21482.  a number of different alternatives. Our first attempt was to have DARWIN
  21483.  write out a simple comma-delimited ASCII text file.
  21484.  
  21485.  "But then the Microsoft Excel development people clued us in to the
  21486.  CE_BIFF format. The availability of different fonts and the ability to set
  21487.  results in a single cell, as well as the accuracy of the CE_BIFF format,
  21488.  made it immediately apparent that CE_BIFF was the way to go. It was
  21489.  straightforward and well documented by Microsoft. With two or three days
  21490.  of research and two or three days of coding, we had it working."
  21491.  
  21492.  Canny now considers Microsoft Excel to be a cooperative environment
  21493.  because it can be used as a Windows programming tool or an adjunct. "You
  21494.  write programs for things that Microsoft Excel does not specialize in, and
  21495.  you send to Microsoft Excel the jobs it does specialize in," he says.
  21496.  "People want calculations, editing, and graphs to make it nice for the
  21497.  boss. Microsoft Excel already does this very well, and we didn't see
  21498.  reinventing those tasks in DARWIN."
  21499.  
  21500.  For all its strengths, the Microsoft Excel environment has its
  21501.  frustrations as well, Hayden says. "Microsoft Excel appears to break
  21502.  Microsoft's own recommended standard that you never hog memory, that the
  21503.  application give it up as soon as it finished. Microsoft Excel goes out
  21504.  and asks, 'How much memory is here? Give me all of it.' It is breaking
  21505.  the friendliness standards."
  21506.  
  21507.  In contrast to Microsoft Excel, which uses all the available memory,
  21508.  DARWIN uses about 175Kb of memory. It takes up the most memory (just over
  21509.  300Kb) when you are using the constraint dialog box, the pull-down menu
  21510.  that lets users set parameters on their queries.
  21511.  
  21512.  
  21513.  Windows Challenges
  21514.  
  21515.  Hayden says he felt challenged by the need to reconcile himself to the
  21516.  device-independent nature of the Windows environment. Canny concurs:
  21517.  "Windows is a very different programming environment from the old
  21518.  sequential model, in which program control flow goes to the first function,
  21519.  the second function, the third function, goes back to the second function,
  21520.  and finishes. With Windows, you have a series of independent, often
  21521.  asynchronous processes, triggered by another process. That is a very new
  21522.  concept for most people who work in the field." Hayden adds, "You've got to
  21523.  follow the modular specs. If you start playing tricks, you're dead."
  21524.  
  21525.  Hayden feels that Windows is an important step forward in application
  21526.  development. "Windows is like C at the very beginning: a beautiful
  21527.  language, but with no libraries. That means initially developing an
  21528.  application is very difficult. You have to rebuild repetitive routines,
  21529.  although people are starting to release some Windows libraries to make
  21530.  things easier. But once you understand it, Windows is really good. It is a
  21531.  liberating mindset."
  21532.  
  21533.  
  21534.  SQL
  21535.  
  21536.  The design team appreciates SQL because of the rigorous mathematical
  21537.  underpinning established by E.F. Codd in his original paper on relational
  21538.  databases ("A Relational Model of Data for Shared Data Banks,"
  21539.  Communications of the ACM 13, No. 6, June 1970). According to Pizi, "This
  21540.  mathematical basis is our assurance that a database designed with the
  21541.  techniques of normalization will serve the emerging and changing needs
  21542.  of the organization in the future. It gives us a firm foundation on which
  21543.  to build."
  21544.  
  21545.  This appreciation of SQL is reflected in some of the design considerations
  21546.  that went into DARWIN. As Pizi points out, he wanted to remain true to what
  21547.  SQL actually is, namely a rigorously defined query language. Therefore, when
  21548.  DARWIN generates a SQL query, it creates a pure set of SQL statements that
  21549.  have no other purpose than to go out and extract data.
  21550.  
  21551.  DARWIN itself has no ability to format data or set up column headings; a
  21552.  number of the current implementations of SQL include user shells to
  21553.  handle such things. Pizi feels that this is a report writer issue, the
  21554.  execution of which should be handled locally at the user's "personal"
  21555.  machine. According to Pizi, "An IBM 3090, for example, should not be
  21556.  spending any of its time formatting data and worrying about where to put
  21557.  it; you shouldn't use the mainframe to manipulate the cursor." This
  21558.  reflects the design team's view of what distributed processing is all
  21559.  about.
  21560.  
  21561.  This approach isolates DARWIN from the data it is working with, which
  21562.  could come from any database. According to Hayden, "DARWIN itself doesn't
  21563.  know anything about the database." This isolation allows Corporate MIS's
  21564.  database designers to use any database technology they want.
  21565.  
  21566.  
  21567.  SQL API and DDE
  21568.  
  21569.  DARWIN speaks to a SQL Applications Programming Interface (SQL API) that
  21570.  Hayden wrote. The interface translates DARWIN's query output into whatever
  21571.  vendor-specific APIs may be out there. From the workstation designer's
  21572.  perspective, DARWIN will become a tool that offers the designer a virtual
  21573.  SQL interface to all of Merrill Lynch's relational database subsystems.
  21574.  
  21575.  According to Pizi, eventually "Windows' Dynamic Data Exchange [DDE]
  21576.  facility will be the pipeline to these APIs." Says Hayden, "DDE fits in
  21577.  where DARWIN talks to another application that is doing the database
  21578.  querying, in this case the SQL API. In the future, when we go to different
  21579.  databases simultaneously, DARWIN as a program does not have to be
  21580.  re-released." The SQL API will simply be modified as necessary for each
  21581.  different database. It sits on one end of DARWIN, and DDE will handle the
  21582.  interface between the two.
  21583.  
  21584.  "Since DARWIN is growing into a large and complex system with more than
  21585.  10,000 lines of code, the question of managing complexity has to be
  21586.  raised," says Canny. According to Pizi, the current design falls into
  21587.  several categories: 48 percent of the code is devoted to Windows
  21588.  overhead, 32 percent makes up the SQL API, 18 percent is dedicated to
  21589.  graphics, and 2 percent of the code deals with creating CE_BIFF files.
  21590.  
  21591.  The one piece of advice that Canny has for other Windows developers is to
  21592.  "make sure that all Windows code is built in a modular fashion. With
  21593.  DARWIN, we need to be able to make additions to it without upsetting what
  21594.  already exists. The program needs to be organized into separate modules,
  21595.  with simple input and simple output that is well defined."
  21596.  
  21597.  The SQL API is an example of this design goal. "Our SQL API isn't really
  21598.  part of Windows," said Hayden. "It is more like a device driver. It breaks
  21599.  DARWIN off from the database." Based on the SQL library of Gupta's LAN-
  21600.  based SQLBase product, the core of the SQL API is founded on seven "shell"
  21601.  functions, which are shown in Figure 17.
  21602.  
  21603.  According to Pizi, this ability to insulate DARWIN from the particular SQL
  21604.  engine is almost as important as DARWIN's point-and-click queries. It
  21605.  relieves the database administrator of the need to support multiple
  21606.  proprietary report writers, which are typically tied to specific database
  21607.  engines. Most importantly, everything conforms to only one standard.
  21608.  
  21609.  The DARWIN-Microsoft Excel combination anticipates in some ways the
  21610.  integration promised by the MS(R) OS/2 SQL Server and the IBM OS/2 Extended
  21611.  Edition. The Merrill Lynch developers say they will enhance the SQL API,
  21612.  which now interfaces to Gupta Technologies' SQLBase engine, to support
  21613.  SQL Server and the IBM product as they become available. They expect most
  21614.  leading database vendors to respond to user demand for a standardized
  21615.  interface between SQL engines and front-end user interfaces.
  21616.  
  21617.  
  21618.  Virtual Data Dictionary
  21619.  
  21620.  In order to communicate with any given database, DARWIN must understand
  21621.  that system's data dictionary. DARWIN also needs certain information that
  21622.  existing data dictionaries do not provide, such as printname, defined
  21623.  column widths, and standard data types. "Our virtual dictionary is a
  21624.  group of seven tables that supplement all the other data dictionaries. All
  21625.  DARWIN requires is that these seven tables be present in those data
  21626.  dictionaries," says Canny. If the tables are present in a given data
  21627.  dictionary, DARWIN can communicate with them (see Figure 18 for an
  21628.  example of one of these tables).
  21629.  
  21630.  
  21631.  What DARWIN Really Is
  21632.  
  21633.  As Pizi puts it, DARWIN is another tool that the application programmer
  21634.  will add to his or her collection of development tools. It simplifies the
  21635.  designer's work because the interface to the data and the user are already
  21636.  in place. It frees the designer to spend more time dealing with what the
  21637.  end user of an application actually needs and provides a standard means
  21638.  for creating a design.
  21639.  
  21640.  But what about end users? "It's not completely clear whether DARWIN is a
  21641.  tool for end users or application developers. It falls somewhere in
  21642.  between," Pizi says. He does not expect DARWIN to be released as a
  21643.  standalone product, though he knows that plenty of people will ask for it.
  21644.  However, Pizi does think that users will eventually push the product
  21645.  further ahead with their own Microsoft Excel macros. "There are rich
  21646.  possibilities here that we intend to explore," he says. Even Microsoft
  21647.  Excel's developers were excited when they saw a demonstration of DARWIN
  21648.  working with the spreadsheet, and now they closely follow the Merrill
  21649.  Lynch product's development, according to Pizi. "We're taking a tool
  21650.  that they produced and actually using it the way they intended."
  21651.  
  21652.  Canny and Hayden say DARWIN is just a first step toward Merrill Lynch's
  21653.  goal of vendor independence in both workstation hardware and application
  21654.  software by moving the company's development platform to a common,
  21655.  stable, graphics-oriented user interface: Windows 2.03 and Windows/386
  21656.  today, OS/2 Presentation Manager in the future. In fact, Pizi and his
  21657.  team eagerly await the official release of the Presentation Manager. They
  21658.  look forward to its rich set of graphical tools and especially to the
  21659.  freedom from the DOS memory limitations that OS/2 will offer.
  21660.  
  21661.  Despite the somewhat steep learning curve, DARWIN is now falling into
  21662.  place, and future development looks promising. As Canny says, "We are now
  21663.  in the process of sizing up what we have, cleaning it up, and applying a
  21664.  more structured approach. We don't move forward as quickly, but we no
  21665.  longer step backward either. Also, this approach allows us to be more
  21666.  predictable with our releases. This is very important now that we've
  21667.  gotten management's attention; they ask us for promises and expect us to
  21668.  deliver on them. We've made the transition from a 'no questions asked'
  21669.  R&D group to a more traditional and responsible systems development
  21670.  group." Concludes Pizi, "We're producing real tools. This isn't a toy
  21671.  any longer."
  21672.  
  21673.  
  21674.  Figure 1:  DARWIN acts as an intermediary, or "front end" between Merrill
  21675.             Lynch's SQL databases and the analysis and reporting applications
  21676.             of its users.
  21677.  
  21678.  ┌───────────────┐                    ┌───────────────────────────────────┐
  21679.  │▓▓▓▓▓▒▒▒▒▒░░░░░│█                   │─██────────────Darwin────────────│█
  21680.  │▓▓▓ Merrill  ░░│█─────Queries──────┤┌────────────────────────────────┐│█
  21681.  │▓▓▓  Lynch   ░░│█                   ││ DARWIN                         │▒│█
  21682.  │▓▓▓ Database ░░│█                   ││                                │▒│█
  21683.  │▓▓▓ Universe ░░│█                   ││    Data Acces and Reporting    │▒│█
  21684.  │▓▓▓▓▓▒▒▒▒▒░░░░░│█──Requested Data──││        Through Windows         │▒│█
  21685.  └───────────────┘█                   ││                                │▒│█
  21686.   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                   │└────────────────────────────────┘│█
  21687.                                       │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
  21688.                                       └───────────────────────────────────┘█
  21689.  ┌───────────────────────────────────┐ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  21690.  ├─██───────Microsoft Excel────────│█              Formatted
  21691.  │┌──┬─────┬─────┬─────┬─────┬─────┐│█               Output
  21692.  │├──┼─────┼┌─────────────┐──┼─────┤▒│█─────────────────┘
  21693.  │├──┼─────┼│  Microsoft  │──┼─────┤▒│█
  21694.  │├──┼─────┼│    Excel    │──┼─────┤▒│█
  21695.  │├──┼─────┼└─────────────┘──┼─────┤▒│█
  21696.  │├──┼─────┼─────┼─────┼─────┼─────┤▒│█──User Formatted Reports──
  21697.  │└──┴─────┴─────┴─────┴─────┴─────┘│█
  21698.  │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
  21699.  └───────────────────────────────────┘█
  21700.   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  21701.  
  21702.  
  21703.  Figure 2:  A detailed view of Merril Lynch's proposed front-end/back-end
  21704.             interconnections.
  21705.  
  21706.           Host Computer
  21707.  ┌─────────────────────────────┐
  21708.  │             MVS             │
  21709.  ├─────┬────────┬─────┬────────┤
  21710.  │     │Teradata│     │  CICS  │
  21711.  │ TSO │Director│ DB2 ├────────┤
  21712.  │     │Program │     │SQL Host│
  21713.  └──┬──┴───┬────┴──┬──┴─┬───┬──┘
  21714.     │      │       └────┘   └─────────────────────LU 6.2 APPC/PC────┐
  21715.     │      └─────────────────────────────────────────────┐          │
  21716.     └──────────────────────────────────┐                 │          │
  21717.            ┌────────────────┐    ┌───────────┐    ┌───────────┐   │
  21718.        ┌───┤ Communications │    │ Interface  │    │ Interface  │   │
  21719.        │   │ Processor      │    │ Processors │    │ Processors │   │
  21720.        │   └───────┬────────┘    └─────┬──────┘    └─────┬──────┘   │
  21721.        │           │                   │                 │          │
  21722.        │   ┌───────┴───────────────────┴─────────────────┴──────┐   │
  21723.        │   │                       Ynet                         │   ≈
  21724.        │   └───┬──────────────┬──────────────┬──────────────┬───┘   │
  21725.        │       │              │              │              │       │
  21726.        │ ┌─────┴─────┐  ┌─────┴─────┐  ┌─────┴─────┐  ┌─────┴─────┐ │
  21727.        │ │ Access    │  │ Access    │  │ Access    │  │ Access    │ │
  21728.        │ │ Module    │  │ Module    │  │ Module    │  │ Module    │ │
  21729.        │ │ Processor │  │ Processor │  │ Processor │  │ Processor │ │
  21730.        │ └─────┬─────┘  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘ │
  21731.        │       │              │              │              │       │
  21732.        │  ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓   │
  21733.        │  ▓▒░Data░▒▓     ▓▒░Data░▒▓     ▓▒░Data░▒▓     ▓▒░Data░▒▓   │
  21734.        │  ▓▒Storage▓     ▓▒Storage▓     ▓▒Storage▓     ▓▒Storage▓   │
  21735.        │  ▓▒░Units▒▓     ▓▒░Units▒▓     ▓▒░Units▒▓     ▓▒░Units▒▓ ┌─┴───────┐
  21736.        │  ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓     ▓▒░░░░░░▒▓ │ SQL     │
  21737.        │                                                          │ Network │
  21738.     TCP/IP                                                        │ Gateway │
  21739.     OSI                 ┌─────────────┐                           └─┬───────┘
  21740.     XNS (June           │ DARWIN      │                             │
  21741.     1988)               │ Workstation │                             │
  21742.   ┌────┴───────┐        └──────┬──────┘                             │
  21743.   │ Function   ├────────┬──────┴─────NetBIOS LAN──────┬─────────────┴─┐
  21744.   │ Call       │ ┌──────┴──────┐┌──────┴──────┐┌──────┴──────┐  ┌─────┴────┐
  21745.   │ Translator │ │ DARWIN      ││ DARWIN      ││ DARWIN      │  │ SQL      │
  21746.   └────────────┘ │ Workstation ││ Workstation ││ Workstation │  │ Database │
  21747.                  └─────────────┘└─────────────┘└─────────────┘  │ Server   │
  21748.                                                                 └──────────┘
  21749.  
  21750.  
  21751.  Figure 15:  DARWIN: Top Level Dataflow Diagram
  21752.  
  21753.                     ┌──────────────┐
  21754.                     │ Standard SQL │
  21755.                     │ Text Queries │
  21756.                     └┬────────────┘
  21757.  ┌───────────────┐   │            │   ┌───────────────────────────────────┐
  21758.  │▓▓▓▓▓▒▒▒▒▒░░░░░───┘            └───┼─██────────────Darwin────────────│█
  21759.  │▓▓▓ Merrill  ░░│█                   │┌────────────────────────────────┐│█
  21760.  │▓▓▓  Lynch   ░░│█──────────────────┤│ DARWIN                         │▒│█
  21761.  │▓▓▓ Database ░░│█  NetBIOS Network  ││                                │▒│█
  21762.  │▓▓▓ Universe ░░│█──────────────────││    Data Acces and Reporting    │▒│█
  21763.  │▓▓▓▓▓▒▒▒▒▒░░░░░│█                   ││        Through Windows         │▒│█
  21764.  └───────────────┘█      ┌────────────│                                │▒│█
  21765.   ▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀      │            │└────────────────────────────────┘│█
  21766.              ┌──────────┴┐       ┌───┤ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
  21767.              │   Result   │       │   └─────────────────────────────────┘█
  21768.              │   Table    │       │     ▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀│▀│▀▀▀
  21769.              └────────────┘       │   Mouse and            Graphical  │ │
  21770.                             ┌─────┴─┐ Keyboard Input        Display   │ │
  21771.                             │Results│   ╔═╧══════════════╗     │      │ │
  21772.                         ┌───┤ Table │   ║ ░░░░░░░░░░░░░░ ─────┘      │ │
  21773.                         │   └───────┘   ║ ░░░░░░░░░░░░░░ ║█         Control
  21774.                         │               ║ ░░░░░░░░░░░░░░ ║█           │ │
  21775.                   ▓▓▓▒░░░░▒▓▓▓         ║ ░░░░░░░░░░░░░░ ║█         Messages
  21776.                   ▓▓▓▒░░░░░▒▓▓▓       ╔═╝────────────────╚═╗          │ │
  21777.                   ▓▓▓CE_BIFF▓▓▓       ║   ║█         │ │
  21778.                   ▓▓▓ Files ▓▓▓       ╚═══════════════════╝█         │ │
  21779.                   ▓▓▓▒░░░░░▒▓▓▓         ▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀│▀▀▀▀         │ │
  21780.                   ▓▓▓▒░░░░░▒▓▓▓                 User  │ │             │ │
  21781.                         │                             │ │             │ │
  21782.                         │                             │ │             │ │
  21783.                         │            ┌────────────────┴───────────────┴┐
  21784.                         │            ├─██───────Microsoft Excel────────│█
  21785.     ┌───────────┐       └────────────┌──┬─────┬─────┬─────┬─────┬─────┐│█
  21786.     │≡≡≡≡≡≡≡≡≡≡≡├┐                   │├──┼─────┼┌─────────────┐──┼─────┤▒│█
  21787.     │≡≡≡≡≡≡≡≡≡≡≡│├┐                  │├──┼─────┼│  Microsoft  │──┼─────┤▒│█
  21788.     │≡≡≡≡≡≡≡≡≡≡≡│││                  │├──┼─────┼│    Excel    │──┼─────┤▒│█
  21789.     │≡≡≡≡≡≡≡≡≡≡≡│││─User Formatted──┤├──┼─────┼└─────────────┘──┼─────┤▒│█
  21790.     │≡≡≡≡≡≡≡≡≡≡≡│││     Reports      │├──┼─────┼─────┼─────┼─────┼─────┤▒│█
  21791.     │≡≡≡≡≡≡≡≡≡≡≡│││                  │└──┴─────┴─────┴─────┴─────┴─────┘│█
  21792.     └┬──────────┘││                  │ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ ╝│█
  21793.      └┬──────────┘│                  └───────────────────────────────────┘█
  21794.       └───────────┘                   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  21795.  
  21796.  
  21797.  Figure 16:  DARWIN.XLM
  21798.  
  21799. ╓┌───┌───────────────────────────────────────────────────────────────────────
  21800.                         A
  21801.   1  DARWIN Macro
  21802.   2  PC Software Development
  21803.   3
  21804.   4
  21805.   5
  21806.   6  =APP.MAXIMIZE()
  21807.   7  =ECHO(FALSE)
  21808.   8  =ADD.MENU(1,B6:C8)
  21809.   9  =ADD.MENU(2,B6:C8)
  21810.  10  =ADD.MENU(3,B6:C8)
  21811.  11  =ADD.MENU(4,B6:C8)
  21812.  12  =ADD.MENU(5,B6:C8)
  21813.  13  =ADD.MENU(6,B6:C8)
  21814.  14  =EXEC("C:\WINDOWS\DARWIN\DARWIN.EXE",3
  21815.  15  =HIDE()
  21816.  16  =RETURN()
  21817.  17
  21818.  18  OUTPUT
  21819.  19  =ECHO(FALSE)
  21820.  20  =IF(GET.DOCUMENU(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(A29),GOTO(A21))
  21821.                         A
  21822. 20  =IF(GET.DOCUMENU(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(A29),GOTO(A21))
  21823.  21  =DIRECTORY("C:\WINDOWS\DARWIN")
  21824.  22  =OPEN("UNNAMED.XLS")
  21825.  23  =COLUMN WIDTH(22,"C1")
  21826.  24  =COLUMN WIDTH(22,"C2")
  21827.  25  =COLUMN WIDTH(22,"C3")
  21828.  26  =COLUMN WIDTH(22,"C4")
  21829.  27  =DISPLAY(FALSE,FALSE,TRUE,TRUE,0)
  21830.  28  =FULL(TRUE)
  21831.  29  =RETURN()
  21832.  30
  21833.  31  LAYOUT
  21834.  32  =IF(GET.DOCUMENT(1,"UNNAMED.XLS")="UNNAMED.XLS",GOTO(CLOSE),GOTO(DARWIN))
  21835.  33  =APP.ACTIVATE("DARWIN",FALSE)
  21836.  34  =SEND.KEYS("%{F10}",TRUE)
  21837.  35  =APP.MINIMIZE()
  21838.  36  =RETURN()
  21839.  37
  21840.  38
  21841.  39
  21842.                         A
  21843. 39
  21844.  40  CLOSE
  21845.  41  =ACTIVATE("UNNAMED.XLS")
  21846.  42  =CLOSE(FALSE)
  21847.  43  =APP.ACTIVATE("DARWIN",FALSE)
  21848.  44  =SEND.KEYS("%{F10}",TRUE)
  21849.  45  =APP.MINIMIZE()
  21850.  46  =RETURN()
  21851.  
  21852.  
  21853.  
  21854.  Figure 17:  DARWIN SQLBase Interface Shell Functions
  21855.  
  21856.   1
  21857.  
  21858.   BOOL SQLExecute(string, bindstring);
  21859.  
  21860.       LPSTR  string;      /* null terminated SQL statement */
  21861.       LPSTR  bindstring;  /* null terminated bind data */
  21862.  
  21863.  Returns:
  21864.  
  21865.   TRUE if completed correctly.
  21866.   FALSE if error.
  21867.  
  21868.  ───────────────────────────────────────────────────────────────────────────
  21869.  Note:
  21870.    The bindstring may be NULL, indicating that there is no bind value.
  21871.  ───────────────────────────────────────────────────────────────────────────
  21872.  
  21873.   2
  21874.  
  21875.   int SQLFetchNextRow();
  21876.  
  21877.  Returns:
  21878.  
  21879.   > 0 : number of fields in the row
  21880.   = 0 : no more information to fetch (past EOF)
  21881.   = ──1 : ERROR occurred
  21882.  
  21883.  ───────────────────────────────────────────────────────────────────────────
  21884.  Note:
  21885.    This routine fetches the next logical row from the database into a SQL
  21886.    API internal structure for later use by SQLGetField.
  21887.  ───────────────────────────────────────────────────────────────────────────
  21888.  
  21889.   3
  21890.  
  21891.   LPSTR SQLGetField(fieldnumber, datatype, flag);
  21892.  
  21893.       int  fieldnumber;       /* desired field (first field=1) */
  21894.       LPINT       datatype;   /* datadictionary/ metaphore datatypes */
  21895.       BOOL        flag;       /* TRUE=remove leading & trailing blanks */
  21896.  
  21897.  Returns:
  21898.  
  21899.   LPSTR, which must be cast to the correct type for usage.
  21900.   NULL if field is null.
  21901.  
  21902.  ───────────────────────────────────────────────────────────────────────────
  21903.  Notes:
  21904.    Setting flag = TRUE is suggested because SQLBase pads fields to maximum
  21905.    field width.
  21906.  
  21907.    If returned data type =
  21908.  
  21909.     string   ->   cast as LPSTR
  21910.     long     ->   cast as LPLONG
  21911.     date     ->   cast as LPDOUBLE
  21912.     double-> cast as LPDOUBLE
  21913.  
  21914.     typedef double FAR *LPDOUBLE /* defined in SQLAPI.H */
  21915.     typedef long int FAR *LPLONG /* defined in SQLAPI.H */
  21916.  ───────────────────────────────────────────────────────────────────────────
  21917.  
  21918.   4
  21919.  
  21920.   LPSTR SQLGetFieldAsText(fieldnumber, datatype, flag);
  21921.  
  21922.  Returns:
  21923.  
  21924.   LPSTR to a NULL-terminated string.
  21925.   NULL if field is null.
  21926.  
  21927.  ───────────────────────────────────────────────────────────────────────────
  21928.  Note:
  21929.    Setting flag = TRUE is suggested because SQLBase pads fields to maximum
  21930.    field width. Numericals are padded to 27 digits in ASCII representation.
  21931.  ───────────────────────────────────────────────────────────────────────────
  21932.  
  21933.  If returned data type =
  21934.  
  21935.   string   ->   use as LPSTR
  21936.   long     ->   use as LPLONG
  21937.   date     ->   use as LPDOUBLE
  21938.   double-> use as LPDOUBLE
  21939.  
  21940.   typedef double FAR *LPDOUBLE /* defined in SQLAPI.H */
  21941.   typedef long int FAR *LPLONG /* defined in SQLAPI.H */
  21942.  
  21943.   5
  21944.  
  21945.   BOOL SQLtoDlgListBox(string, bindstring, hDlg, DlgItem);
  21946.  
  21947.       LPSTR        string;     /* SQL SELECT statement */;
  21948.       LPSTR        bindstring; /* null terminated bind data */
  21949.       HWND hDlg;               /* handle of a dialog box */
  21950.       WORD DlgItem;            /* item number of destination list box */
  21951.  
  21952.  Returns:
  21953.  
  21954.   TRUE if completed successfully.
  21955.   FALSE if error.
  21956.  
  21957.  ───────────────────────────────────────────────────────────────────────────
  21958.  Note:
  21959.    This function allows one function call (from a dialog box) to perform a
  21960.    complete SQLBase execute and fetch sequence. The fields are laid out as a
  21961.    single list box item with the fields separated by a blank space. Field
  21962.    width is maintained as returned by SQLBase as strings.
  21963.  ───────────────────────────────────────────────────────────────────────────
  21964.  
  21965.   6
  21966.  
  21967.   int SQLToBiffFile(select, bindstring, bifffilename);
  21968.  
  21969.       LPSTR     select;        /* SQL SELECT statement */
  21970.       LPSTR     bindstring;    /* null-terminated bind data */
  21971.       LPSTR     bifffilename;  /* filename for BIFF file */
  21972.  
  21973.  Returns:
  21974.  
  21975.   Number of rows written.
  21976.   ─1 if error occurred.
  21977.  
  21978.  ───────────────────────────────────────────────────────────────────────────
  21979.  Note:
  21980.    This function allows one function call to perform a complete SQLBase
  21981.    execute-and-fetch sequence. The fields are laid out as cells within a new
  21982.    Excel Biff file.
  21983.  ───────────────────────────────────────────────────────────────────────────
  21984.  
  21985.  Bindstring and bifffilename may be NULL.
  21986.  
  21987.   7
  21988.  
  21989.   int SQLErrorNum();
  21990.  
  21991.  Returns:
  21992.  
  21993.  An integer defining the type of error, if any, that occurred.
  21994.  
  21995.  ───────────────────────────────────────────────────────────────────────────
  21996.  Note:
  21997.    To maintain the design that hides SQLBase specifics, the SQLBase internal
  21998.    error number and information will be translated into DARWIN internal
  21999.    values yet to be defined in SQLAPI.H.
  22000.  ───────────────────────────────────────────────────────────────────────────
  22001.  
  22002.  
  22003.  Figure 18:  DAR_COLUMNS contains information on the columns that comprise a
  22004.              table. For each column in a table there is one row in
  22005.              DAR_COLUMNS. Each row in the table is a column in DAR_COLUMNS.
  22006.  
  22007. ╓┌──────────────┌────────────────┌─────────────┌─────────────────────────────╖
  22008.  ┌─────COLUMN NAME─────┐
  22009.  INTERNAL       EXTERNAL         DATA TYPE     DESCRIPTION
  22010.  ┌─────COLUMN NAME─────┐
  22011.  INTERNAL       EXTERNAL         DATA TYPE     DESCRIPTION
  22012.  
  22013.  Tablename      Internal         Char (15)     The internal name of the
  22014.                 Table Name                     table that contains the
  22015.                                                column.
  22016.  
  22017.  Columnname     Internal         Char (15)     The internal name  of the
  22018.                 Column Name                    column.
  22019.  
  22020.  Printname      Column Print     Char (30)     The print name or external
  22021.                 Name                           name of the column as it
  22022.                                                appears in DARWIN's
  22023.                                                graphical representation
  22024.                                                of the data.
  22025.  
  22026.  Datatype       Data Type        Integer       The data type of the column.
  22027.                                                 0 = Integer
  22028.                                                 1 = Real (floating point)
  22029.                                                 2 = Character
  22030.                                                 3 = Date
  22031.  ┌─────COLUMN NAME─────┐
  22032.  INTERNAL       EXTERNAL         DATA TYPE     DESCRIPTION
  22033.                                                3 = Date
  22034.  
  22035.  Precision      Field Width      Integer       Contains the maximum
  22036.                                                number of digits or
  22037.                                                characters for datatypes
  22038.                                                INTEGER, REAL, or
  22039.                                                CHARACTER. If the
  22040.                                                column is a date datatype
  22041.                                                the field contains a 0.
  22042.  
  22043.  Fraction       Fraction Size    Integer       The number of digits to the
  22044.                                                right of the decimal point.
  22045.                                                This is used for real
  22046.                                                (floating point) numbers.
  22047.  
  22048.  Resolution     Data             Integer       This column is not
  22049.                 Interpretation                 supported. The field = 0.
  22050.  
  22051.  Keyusage       Key?             Integer       Indicates if the column is
  22052.  ┌─────COLUMN NAME─────┐
  22053.  INTERNAL       EXTERNAL         DATA TYPE     DESCRIPTION
  22054. Keyusage       Key?             Integer       Indicates if the column is
  22055.                                                part of the primary key.
  22056.                                                 0 = Column is not part of
  22057.                                                    primary key
  22058.                                                 1 = Column is part of
  22059.                                                    primary key
  22060.  
  22061.  Domain         Domain           Integer       This column is not used.
  22062.                                                The field = 0.
  22063.  
  22064.  Navalue        N/A Value        Char (30)     This column is not used.
  22065.                                                The field = 0.
  22066.  
  22067.  Naused         N/A Used         Integer       This column is not used.
  22068.                                                The field = 0.
  22069.  
  22070.  Dorder         Display Order    Integer       The order in which the
  22071.                                                columns in the
  22072.                                                table are displayed.
  22073.  ┌─────COLUMN NAME─────┐
  22074.  INTERNAL       EXTERNAL         DATA TYPE     DESCRIPTION
  22075.                                               table are displayed.
  22076.  
  22077.  Description    Description      Char (200)    A text description of the
  22078.                 of Column                      column.
  22079.  
  22080.  ████████████████████████████████████████████████████████████████████████████
  22081.  
  22082.  CodeView for Windows Provides an Interactive Debugging Environment
  22083.  
  22084.  Paul Yao and David Durant
  22085.  
  22086.  Microsoft(R) Windows and CodeView(R) are two products that have made a
  22087.  significant impact on the PC world, mostly because of their highly
  22088.  interactive user interfaces. The CodeView user interface is based on
  22089.  character-oriented windows and makes a debugging tool accessible in a way
  22090.  that was not previously available on microcomputers. The Windows user
  22091.  interface is a suggested style and a set of powerful tools used to
  22092.  implement this style.
  22093.  
  22094.  This article will examine CodeView for Windows (CVW), a product that is
  22095.  familiar to many DOS programmers, but, until now, has not been "Windows-
  22096.  aware." First, we'll look at why an interactive debugger is useful to
  22097.  programmers, followed by an examination of the major stumbling blocks of
  22098.  Windows programming and how to avoid some of them. After a brief
  22099.  discussion of CVW's hardware requirements, we will relate our
  22100.  experiences using a beta copy of CVW. Finally, a sample debugging
  22101.  session will trace the messages involved in the creation of an
  22102.  application's main window.
  22103.  
  22104.  CodeView employs many of the features found in Windows applications (see
  22105.  Figure 1), including drop-down menus, dialog boxes, windows, and mouse
  22106.  support. It is paradoxical that Windows programmers have for so long been
  22107.  unable to use a tool so akin to their own environment.
  22108.  
  22109.  There is a simple explanation for this: the two products were created
  22110.  independently by different development groups within Microsoft. CodeView
  22111.  was not originally developed with Windows applications in mind; it was
  22112.  geared toward the development of DOS applications.
  22113.  
  22114.  Although its interface is similar to that of Windows, CVW is not itself
  22115.  a Windows application in the usual sense. Instead, it is a special version
  22116.  of the original CodeView that has been made aware of the way in which the
  22117.  Windows memory manager juggles memory. What strikes you first when using
  22118.  CVW is that it does not share the screen with Windows; rather, it sends
  22119.  output to its own display monitor in exactly the same way that Symdeb,
  22120.  another interactive debugger, does.
  22121.  
  22122.  
  22123.  Interactive Debugger
  22124.  
  22125.  In the Windows programming classes that we teach, we are constantly
  22126.  astounded by the number of people who have never used an interactive
  22127.  debugger. There are certainly useful alternatives to an interactive
  22128.  debugger, such as the embedding of calls to MessageBox to display data
  22129.  and the inclusion of calls to fprintf to output trace information to a
  22130.  communications port. However, these techniques are cumbersome when
  22131.  compared with the power and flexibility available in an interactive
  22132.  debugger, where you have total control of your computer while your
  22133.  program is processing.
  22134.  
  22135.  Without an interactive debugger, you must stop, rewrite, and recompile
  22136.  your code before you can begin execution. An interactive debugger offers
  22137.  you the flexibility to decide how to investigate the operation of
  22138.  your program. It allows for spontaneous interaction with a running
  22139.  program at a level not otherwise possible.
  22140.  
  22141.  Getting started with an interactive debugger is no trivial task,
  22142.  however. Setting up a debugging environment takes time, and you must
  22143.  achieve a minimum proficiency with a debugger before it is truly useful.
  22144.  With the tight development schedules that programmers face, the
  22145.  preparation of a debugging environment somehow never gets done. Still, it
  22146.  is definitely worth the time; people who start working with an
  22147.  interactive debugger reap enormous benefits almost immediately.
  22148.  
  22149.  
  22150.  Development Problems
  22151.  
  22152.  Even with an interactive debugger, debugging a Windows application
  22153.  presents some unique challenges. It would be bad enough if the dynamic
  22154.  loading, movement, and discarding of memory objects were the only source
  22155.  of problems. However, the structure of Windows programs, the lack of
  22156.  hardware memory protection, and the need to be good at so many things,
  22157.  such as Windows Software Development Kit (SDK) routines, Intel(R)
  22158.  architecture, and C programming, contribute to the difficulties
  22159.  encountered when coding and debugging Windows applications.
  22160.  
  22161.  For some of these problems, the only solution is time and experience.
  22162.  Windows programming takes everything you know about programming and
  22163.  turns it on its ear──as the saying goes, everything you know is wrong.
  22164.  Taking advantage of the power of Windows/Presentation Manager
  22165.  programming requires you to learn a new way of thinking. Six months of
  22166.  full-time Windows programming, assuming prior experience with C, is a fair
  22167.  estimate.
  22168.  
  22169.  While you are gaining this experience, however, there is error checking
  22170.  available for free, provided by the compiler and Windows, itself. It
  22171.  requires only that you flip the right switches. Here are a number of
  22172.  suggestions for overcoming some common Windows development problems by
  22173.  using these free services.
  22174.  
  22175.  Always use the highest level of compiler warning switches (W2 or W3), and
  22176.  aim for a clean compile.
  22177.  
  22178.  Prototype every routine that you write. For examples of prototyping,
  22179.  just look at the WINDOWS.H file, where you will see declarations like the
  22180.  one shown below:
  22181.  
  22182.    BOOL FAR PASCAL
  22183.    TextOut (HDC, short, short, LPSTR, short);
  22184.  
  22185.  This declaration gives the compiler some very useful information, such
  22186.  as the return value (in the example above, Boolean), the calling
  22187.  convention (FAR and PASCAL), the number of parameters (5), and the type
  22188.  of each parameter.
  22189.  
  22190.  If you prototype your routines, you can quickly and easily eliminate some
  22191.  C programming errors. For example, in the course of development, it is
  22192.  common to add parameters to various routines. If, however, you fail to
  22193.  change every call to a routine that has one more (or one less) parameter,
  22194.  your application will contain a bug.
  22195.  
  22196.  This bug may bite you by causing your application to fail immediately. In
  22197.  this case, you will probably search until you find the problem. Or the bug
  22198.  may sit and wait, creating sporadic and unpredictable problems in your
  22199.  program. Because tthis type of bug can be found almost instantly,
  22200.  prototyping can save you much wasted time and effort and should always be
  22201.  used with a W2 or W3 compiler warning switch.
  22202.  
  22203.  Avoid casting. It is redundant in many situations, and can hide bugs in
  22204.  others. Let the compiler tell you about type mismatches, and in those
  22205.  cases respond by casting.
  22206.  
  22207.  The following casting (the two LPPAINTSTRUCTs and the LPSTR) is
  22208.  redundant.
  22209.  
  22210.    hDC = BeginPaint (hWnd, (LPPAINTSTRUCT)&ps);
  22211.    TextOut  (hDC, 10, 10, (LPSTR)"It's a Long Way to Tipperary",28);
  22212.    EndPaint (hWnd, (LPPAINTSTRUCT)&ps);
  22213.  
  22214.  It was included in the earliest examples of Windows programs, however,
  22215.  because the C compiler did not support prototyping at that time. Every
  22216.  version of the Microsoft C compiler since C Version 4.0 supports prototyping
  22217.  and eliminates the need to cast in many circumstances. Prototyping enables
  22218.  the compiler to promote a near pointer to a far pointer, based on the
  22219.  prototypes in WINDOWS.H.
  22220.  
  22221.  Be sure that you include every window procedure, dialog box procedure,
  22222.  enumeration procedure, callback procedure, and subclass procedure found
  22223.  in the EXPORTS list of your application's module definition (DEF) file.
  22224.  This is a very common error and wastes much time. The lack of compiler and
  22225.  linker error checking in this regard makes this problem difficult to find.
  22226.  
  22227.  Install and regularly use a debugging version of Windows, not to be
  22228.  confused with a Windows-compatible debugger, such as CodeView. You create
  22229.  this by using special copies (from the SDK) of KERNEL.EXE, USER.EXE, and
  22230.  GDI.EXE.
  22231.  
  22232.  A debugging version of Windows performs error checking not available in
  22233.  the retail version of Windows. It catches problems like bad handles and
  22234.  wild pointers and notifies you via a debugging terminal, which you must
  22235.  have ready to receive the fatal exit messages, sometimes referred to as
  22236.  "RIP" (Rest In Peace) codes. Be sure to include the symbol files,
  22237.  KERNEL.SYM, USER.SYM, and GDI.SYM, which are used to display stack trace
  22238.  information when RIP code is generated.
  22239.  
  22240.  With a debugging version of Windows installed, be sure to set the
  22241.  following switches in your WIN.INI file:
  22242.  
  22243.    [kernel]
  22244.    EnableHeapChecking=1
  22245.    EnableFreeChecking=1
  22246.  
  22247.  This causes all free bytes to be filled with the value CH. This protects
  22248.  you in two ways: first, if a wild pointer from your program causes a
  22249.  write to occur in a free block, a fatal exit message is sent to your
  22250.  debugging terminal. Second, you are protected if your program tries to
  22251.  jump to a free block. The value CCH translates into the machine
  22252.  instruction INT 3, which is a call to the debugger. A wild jump, then,
  22253.  causes the debugger to come alive, allowing you to display a stack
  22254.  trace and determine the part of your program from which execution
  22255.  departed. Because of these benefits, the use of these switches is strongly
  22256.  recommended.
  22257.  
  22258.  If you are using Expanded Memory Specification (EMS) memory (or running
  22259.  Windows/386, which has built-in EMS support), make sure you set the
  22260.  following switch in WIN.INI:
  22261.  
  22262.    [kernel]
  22263.    EnableEMSDebug=1
  22264.  
  22265.  This switch makes sure the debugger is told when EMS pages are banked in
  22266.  and banked out and lets you set breakpoints within the discardable code
  22267.  segments of dynamic-link libraries, where many Windows SDK routines
  22268.  reside.
  22269.  
  22270.  
  22271.  Hardware Setup
  22272.  
  22273.  CVW has some unique hardware requirements, such as the need for a second
  22274.  monitor. We will review the choices available and discuss the need for
  22275.  EMS 4.0 memory. If you have been using Symdeb, the setup of the second
  22276.  monitor is the same, and you are ready for CVW.
  22277.  
  22278.  You have two choices: a monochrome display adapter (MDA) with a monochrome
  22279.  monitor or an asynchronous terminal with a null-modem adapter. The first
  22280.  choice is the preferable one, because you can then use CVW in full-screen
  22281.  mode. An asynchronous terminal is useful, though, since you can take
  22282.  advantage of the access to local variables that CVW provides. However, you
  22283.  must operate in line mode without full-screen support.
  22284.  
  22285.  When operating in full-screen mode, CVW lets you use the mouse to select
  22286.  menu items, and control its operation (when CVW has control). In this
  22287.  situation, CVW cooperates nicely by sharing the mouse with Windows.
  22288.  However, you must load a DOS mouse driver, which CVW will then use for its
  22289.  mouse support. Even if you don't plan on using a mouse (in line mode, for
  22290.  example), you still must install a driver for it. Otherwise, the Windows
  22291.  mouse freezes up, although you can still operate Windows with the keyboard
  22292.  interface commands. We hope this problem is corrected before CVW is
  22293.  released.
  22294.  
  22295.  
  22296.  EMS 4.0 Memory
  22297.  
  22298.  CVW requires you to have EMS memory present. CVW takes up 138Kb of main
  22299.  system memory and then employs EMS memory for its code, data, and symbol
  22300.  space. The available options depend on the specific Windows product that
  22301.  you are using. With Windows 2.x, you must have an EMS board and an EMS 4.0
  22302.  or later driver. We experimented with an Intel Above(TM) Board, as well as
  22303.  with an AST(R) RAMpage!(R) board. Both worked well, but you must make sure
  22304.  that you have the correct version of the driver.
  22305.  
  22306.  EMS 4.0 support is built into Windows/386, which CVW can use. There are
  22307.  two things to keep in mind here. First, you must have a minimum of 640Kb
  22308.  main memory and 512Kb extended memory in order for EMS support to be
  22309.  available under Windows/386. Second, you must play a trick on Windows/386
  22310.  so that its EMS support is loaded first; you then start CVW before
  22311.  starting Windows. This process is described in more detail in the
  22312.  documentation.
  22313.  
  22314.  
  22315.  CodeView and Windows
  22316.  
  22317.  Now that CodeView is available for debugging Windows applications,
  22318.  Windows programmers may ask if its debugging capabilities can really be
  22319.  adapted to the unique and extremely complex Windows environment. The
  22320.  answer is yes, but it is a qualified yes. Let's look at CodeView's
  22321.  strengths and weaknesses in the Windows environment──remember that our
  22322.  experience is based on a beta release of the software.
  22323.  
  22324.  
  22325.  Windows Programs
  22326.  
  22327.  As every Windows programmer quickly learns, a Windows application
  22328.  program does not control its own destiny; it lives at the mercy of
  22329.  messages sent by the user, by Windows, by other programs, and by itself.
  22330.  Windows programs do not always have control of the processor; instead
  22331.  they spend most of their time suspended, waiting for a message to arrive.
  22332.  
  22333.  When a message is delivered to a Windows program, it gains control of the
  22334.  processor and must respond to the particular message. The message is often
  22335.  delivered to the GetMessage processing loop. From there control is
  22336.  transferred to the appropriate window procedure. The decision is based
  22337.  on a piece of information that is part of a message, the window handle.
  22338.  
  22339.  Once a window procedure receives a message, it determines the message
  22340.  type and typically branches to a subprocedure referred to as a message
  22341.  procedure. Each message procedure is independent of the others in the
  22342.  sense that the processing of one message cannot assume that some other
  22343.  message has already been received or will subsequently be received. It can
  22344.  only check the current state of the application, such as the currently
  22345.  active cell of a spreadsheet, and react accordingly.
  22346.  
  22347.  The classic example of the extent to which Windows applications must
  22348.  react to messages is the WM_PAINT message. In response to a WM_PAINT
  22349.  message, a window procedure must be capable of recreating the contents
  22350.  of the client area at any time, for any reason, and under any
  22351.  circumstances. A Windows program, then, tends to be reactive; it is the
  22352.  user and not the program that controls the flow of processing.
  22353.  
  22354.  A Windows program can be thought of as a state machine, with the state
  22355.  stored in external variables. Windows programs also differ from normal
  22356.  programs in that they are object-oriented. Window classes, windows,
  22357.  display contexts, GDI objects, and loaded resources are all objects in a
  22358.  general sense: they are identified by a handle, you do not have to be
  22359.  concerned with the underlying data structures, and a given type of
  22360.  object, such as a window, can be manipulated in a standard set of ways
  22361.  (any window can be moved, sized, repainted, sent a message, and so on).
  22362.  
  22363.  Also, Windows is a library-oriented environment, specifically dynamic-
  22364.  link libraries. All the toolkit routines are contained in dynamic-link
  22365.  libraries. You can use dynamic-link libraries to share code, resources,
  22366.  data, and to control the allocation of some limited resource, such as
  22367.  system memory, or a local area network link.
  22368.  
  22369.  
  22370.  CodeView Debugging
  22371.  
  22372.  Programs have two parts: code and data. One of the chief uses of an
  22373.  interactive debugger like CodeView is to enable you to follow the path
  22374.  that your program takes through the code. During the course of such a
  22375.  "trace" operation, you can view and even modify the data on which your
  22376.  code operates. CodeView offers three methods of tracing execution:
  22377.  executing the program one line at a time, executing the program in "slow
  22378.  motion," and executing the program at normal speed but halting its
  22379.  execution at predetermined points within the code or based on prespecified
  22380.  data conditions.
  22381.  
  22382.  Executing the program one line at a time means either one source line or
  22383.  one object instruction at a time, depending on the mode that CodeView is
  22384.  currently in. Running in slow motion means that approximately four
  22385.  instructions per second will be executed and that the display of code and
  22386.  data are synchronized with program execution, continuously updated to
  22387.  reflect the current program location and data values.
  22388.  
  22389.  At any time you are running in slow motion, execution of the program can
  22390.  be halted to permit other interactions with CodeView. Hereafter, running
  22391.  the program in slow motion will be referred to as executing the program,
  22392.  while running the program at normal speed, which requires the Go command,
  22393.  will be referred to as just running the program.
  22394.  
  22395.  The third technique, running the program at normal speed and halting its
  22396.  execution at predetermined points, consists of setting breakpoints,
  22397.  watchpoints, and tracepoints. Setting a breakpoint means specifying a
  22398.  point in the code at which program execution is to halt. This is done by
  22399.  finding that line of code in the code display window and clicking upon it
  22400.  with a mouse or by using the breakpoint set command, bp. When you do
  22401.  this, the line will be displayed in high intensity. You can remove the
  22402.  breakpoint by clicking on the line a second time, which will return it to
  22403.  normal intensity. As many as 19 breakpoints can be set at a time.
  22404.  
  22405.  A brief word about an idiosyncrasy of CVW: CVW indicates that a
  22406.  breakpoint has been set by highlighting a line of code. However,
  22407.  highlighting also indicates the current line when tracing execution. These
  22408.  two unrelated uses make it difficult to determine if the current line is
  22409.  set as a breakpoint or not. To find out, you simply have to trace
  22410.  execution to another line.
  22411.  
  22412.  Just as breakpoints are execution halts set within the code, watchpoints
  22413.  and tracepoints are execution halts based on data values. The watchpoint
  22414.  is a conditional statement involving one or more program variables, such
  22415.  as "character count==80". Program execution halts at the instruction and
  22416.  causes the value of watchpoint to become true.
  22417.  
  22418.  A tracepoint is also a statement involving one or more program variables,
  22419.  such as iIndex, but it need not be a conditional statement. This is
  22420.  because the program is going to halt anytime the value of a tracepoint
  22421.  changes, regardless of what value it changes to.
  22422.  
  22423.  One nice thing about breakpoints, watchpoints, and tracepoints is that
  22424.  they remain in effect even after the program is finished executing. Thus
  22425.  you can set the watchpoints and breakpoints and run the program several
  22426.  times without having to reset them.
  22427.  
  22428.  One not-so-nice thing about watchpoints and tracepoints concerns local
  22429.  variables. Local variables, as most programmers know, are in effect only
  22430.  while the routine in which they are defined is actually being executed
  22431.  and are thus known only to CodeView at that time. Therefore, if you wish
  22432.  to set a watchpoint or a tracepoint involving a local variable, you must
  22433.  first set the breakpoint and execute the program up to that breakpoint so
  22434.  that the halted execution is now within the routine that contains the
  22435.  local variable. Once you have done this you can then define the
  22436.  watchpoints and tracepoints that involve that local variable, remove the
  22437.  breakpoint, and continue to execute or run the program.
  22438.  
  22439.  A limitation of CodeView or any interactive debugger is that input is
  22440.  shared between your program and your debugger. Your program needs input,
  22441.  of course, which it receives in the form of mouse and keyboard input, and
  22442.  debuggers also require input. In the case of CodeView, both keyboard and
  22443.  mouse input can be used. However, if you wish to debug mouse or keyboard
  22444.  input, switching into a debugger can be annoying. There are ways around
  22445.  this, of course, and we mention them not to detract from CodeView, but
  22446.  merely to identify an area that requires special attention when using an
  22447.  interactive debugger.
  22448.  
  22449.  For example, setting a breakpoint to trace through WM_MOUSEMOVE
  22450.  processing can be difficult, if not impossible. Another limitation
  22451.  inherent in any interactive debugger is that, with the interruptions
  22452.  caused by breakpoints and the ability to trace program execution, time-
  22453.  dependent operations can be difficult to debug. Thus, CVW is limited in
  22454.  its ability to work with programs dependent on WM_TIMER messages for their
  22455.  normal operation. Also, if there are external, real-time events that your
  22456.  Windows program must track, the time-dependent portions of your program
  22457.  may be impossible to debug with an interactive debugger.
  22458.  
  22459.  
  22460.  Windows Debugging
  22461.  
  22462.  Given that Windows programs react to messages, can be viewed as state
  22463.  machines, and are in an object-oriented and dynamic-link library
  22464.  environment, how can CVW help in debugging Windows programs? CVW handles
  22465.  the reactive nature of a Windows application program very well because of
  22466.  its ability to trace the flow of execution through code. By setting
  22467.  breakpoints, watchpoints, and tracepoints, you can see which message types
  22468.  are being received and what a program does in response to any given
  22469.  message. Later on, we will do just that by tracing the message traffic
  22470.  associated with the creation of an application's main window.
  22471.  
  22472.  CVW also fits well with the state-machine nature of a Windows application
  22473.  program. Since the state of the application is maintained in program
  22474.  variables and the user can watch those program variables change as the
  22475.  program runs, even halting execution when the variables reach some
  22476.  unexpected value, the user can remain aware of the state of the program as
  22477.  it is running and detect problems in that area.
  22478.  
  22479.  CVW is of limited use in dealing with the object-oriented nature of a
  22480.  Windows program. This is not so much a flaw in CodeView as simply a result
  22481.  of the fact that objects are kept outside the program's data segment. Pens,
  22482.  brushes, fonts, bitmaps, globally allocated memory objects, and the data
  22483.  structures that describe a window are beyond easy access for CodeView.
  22484.  
  22485.  If you wish to look at the contents of a memory object, a cursor, icon, or
  22486.  bitmap, HeapWalker is your best bet. If you want to find out about a GDI
  22487.  object, you can call the GetObject routine to transfer values into a
  22488.  program variable that can then be viewed with CodeView. If you wish to see
  22489.  information on any other object, such as a window, use the appropriate
  22490.  toolkit routine, GetWindowWord for example, to transfer the
  22491.  information to a program variable.
  22492.  
  22493.  CVW is helpful in an object-oriented environment because you can look at
  22494.  handles and pointers, which are, after all, located within your program's
  22495.  variables. By setting tracepoints you can detect changes in the value of
  22496.  handles, and you will be able to trap events like the unintentional
  22497.  overwriting of a handle or pointer value. Also, you can determine when a
  22498.  null handle has been returned and be certain that a failure has
  22499.  occurred.
  22500.  
  22501.  CVW is also useful in working with dynamic-link libraries. CVW permits
  22502.  you to perform source-level tracing into your dynamic-link libraries,
  22503.  giving you a more complete picture of what is happening during the execution
  22504.  of your application.
  22505.  
  22506.  If you want to view the code contained in one of the Windows dynamic-link
  22507.  libraries, such as Kernel, User, or GDI, CVW lets you do this. However,
  22508.  its usefulness depends on whether you understand 8086 assembly language
  22509.  or not. Because you do not have the source code to these dynamic-link
  22510.  libraries, you can only trace through them on a machine-language level.
  22511.  If you are reasonably proficient in reading 8086 assembler, this can be
  22512.  invaluable for determining exactly what an SDK routine is doing under a
  22513.  particular set of circumstances.
  22514.  
  22515.  
  22516.  Sample Session
  22517.  
  22518.  We are now ready to walk through a sample CVW debugging session. Our
  22519.  purpose is to show you how setting a breakpoint on a window procedure's
  22520.  switch statement allows you to watch the messages associated with the
  22521.  creation of a window.
  22522.  
  22523.  
  22524.  Getting Started
  22525.  
  22526.  The program that we will trace is the "Hello Windows" program from the
  22527.  Sample Source Code disk of the Windows Version 2.03 SDK, which you should be
  22528.  able to copy from the Windows SDK to walk through this debugging session
  22529.  yourself.
  22530.  
  22531.  The first step is to modify the program's make file so that it has the
  22532.  appropriate compiler and linker switches. This involves adding the Zi and
  22533.  Od switches to the compile line and the /CO switch to the link line. The
  22534.  complete make file is shown in Figure 2.
  22535.  
  22536.  Once you have recreated HELLO.EXE, you start CVW by using one of the two
  22537.  command lines that are shown in Figure 3. If you are using an MDA adapter
  22538.  with a monochrome display, which enables full-screen mode debugging, use
  22539.  the first command line shown in Figure 3. If you are using an
  22540.  asynchronous terminal with a null-modem adapter as your debugging
  22541.  workstation, which enables line-mode debugging, use the second command line.
  22542.  
  22543.  In the rest of this sample debugging session, full-screen debugging mode
  22544.  is used. When CVW starts, you will see the two screens shown in Figures 4
  22545.  and 5.In Figure 4, you see that CodeView has started. In Figure 5
  22546.  however, you see that Windows itself has not yet started.
  22547.  
  22548.  
  22549.  Setting Breakpoints
  22550.  
  22551.  First, set a breakpoint at the window procedure's switch statement, which
  22552.  involves locating a source line and then either clicking with the left
  22553.  button or using the bp command to indicate the necessary source line.
  22554.  Figure 6 shows a breakpoint set with the bp command highlighted.
  22555.  
  22556.  
  22557.  Go
  22558.  
  22559.  Next, start execution. When you issue the Go command, you first notice
  22560.  that Windows starts running. Figure 7 shows the display screens after
  22561.  Windows has started, when the system has encountered the breakpoint set at
  22562.  the switch statement in the Hello program's window procedure, HelloWndProc.
  22563.  Note that the Windows display contains the familiar blue of the desktop,
  22564.  although nothing currently occupies it. In the CodeView display, you'll see
  22565.  that you have mouse control and are able to issue CodeView commands.
  22566.  
  22567.  
  22568.  Adding to the Watch List
  22569.  
  22570.  At this point, we would like to add the local variable "message" to the
  22571.  watch list. Because we want the value to be displayed in hexadecimal, a type
  22572.  modifier is added to the w? command. We add message to the watch list by
  22573.  typing the following at the CodeView prompt:
  22574.  
  22575.    > w? message, x
  22576.  
  22577.  Figure 8 shows that the "watch window" has been opened and that the
  22578.  current value of message (0024H) is being displayed. A quick look at
  22579.  WINDOWS.H reveals that this value represents the WM_GETMINMAXINFO message.
  22580.  Even though a message has been received for our window, it has not been
  22581.  processed yet. To allow the message to process, we again issue the g
  22582.  (GO) command, after which another breakpoint is encountered, and the
  22583.  display appears as shown in Figure 9. Notice that the CVW display shows
  22584.  that the value of message is 0081H and that nothing seems to have changed
  22585.  in the Windows display.
  22586.  
  22587.  The messages next received are:
  22588.  
  22589.    WM_GETMINMAXINFO
  22590.    WM_NCCREATE
  22591.    WM_CALCSIZE
  22592.    WM_CREATE
  22593.    WM_SHOWWINDOW
  22594.    WM_SETVISIBLE
  22595.    WM_ACTIVATEAPP
  22596.  
  22597.  
  22598.  The Nonclient Area
  22599.  
  22600.  The first message that causes anything to appear on the Windows display
  22601.  is the message WM_NCACTIVATE. The NC part of the message stands for
  22602.  nonclient. As Figure 10 shows, this simply draws the border of the window,
  22603.  along with the System Box and the Minimize and Maximize Boxes.
  22604.  
  22605.  The very next message, WM_GETTEXT, seems to cause the caption to be drawn.
  22606.  In fact, we suspect that the prior WM_NCACTIVATE sent a WM_GETTEXT message
  22607.  to retrieve this Windows message so as to properly display the caption
  22608.  information (see Figure 11).
  22609.  
  22610.  Another group of messages appear that affect the display. These messages
  22611.  are:
  22612.  
  22613.    WM_ACTIVATE
  22614.    WM_SETFOCUS
  22615.    WM_NCPAINT
  22616.    WM_SYNCPAINT
  22617.  
  22618.  
  22619.  Erasing the Background
  22620.  
  22621.  Figure 12 demonstrates the effect of the message WM_ERASEBKGND. Clearly,
  22622.  the background of the window has been erased with the main window's default
  22623.  background brush, which is a white brush. A pair of messages then appears:
  22624.  
  22625.    WM_SIZE
  22626.    WM_MOVE
  22627.  
  22628.  You should note that the size and the move messages appear at window
  22629.  creation time, which is useful to know if you plan to respond to these two
  22630.  messages. Think of these messages as saying, "Your window is changing in
  22631.  size from nothing to something and moving from nowhere to somewhere."
  22632.  
  22633.  
  22634.  Hello Windows!
  22635.  
  22636.  Finally, when a WM_PAINT message appears, the familiar "Hello Windows"
  22637.  message, shown in Figure 13, is displayed. It seems that so many messages
  22638.  have flown by as part of the window's creation process, but there are still
  22639.  two messages coming in, namely:
  22640.  
  22641.    WM_NCHITTES
  22642.    WM_SETCURSOR
  22643.  
  22644.  The last message to appear is the WM_MOUSEMOVE message. This concludes
  22645.  the immediate window creation message traffic. In all, 20 messages were
  22646.  received by this window procedure before the window creation process was
  22647.  complete.
  22648.  
  22649.  At this point, if you move the mouse cursor, another WM_MOUSEMOVE message
  22650.  is sent to the application's window procedure. You need to disable the
  22651.  breakpoint; otherwise you would be trapped forever by mouse movement
  22652.  messages. You do this either by clicking again on the breakpoint (to
  22653.  toggle the breakpoint) or by issuing the following command:
  22654.  
  22655.    > bc 0
  22656.  
  22657.  
  22658.  Quitting CodeView
  22659.  
  22660.  Before you quit CodeView, you need to close down Windows. This is because
  22661.  Windows modifies the system, including several system vectors that must be
  22662.  restored if the system is to operate in a normal fashion. Once this is
  22663.  done, you exit CodeView with the Quit command:
  22664.  
  22665.    > quit
  22666.  
  22667.  
  22668.  A Breakthrough
  22669.  
  22670.  When it becomes available, CodeView for Windows will be a breakthrough for
  22671.  Windows developers. Its interface is intuitive and easy to use. Its ability
  22672.  to access local variables, and to display data structures and evaluate
  22673.  complex C expressions gives it power that puts it in a class by itself.
  22674.  
  22675.  However, its main memory requirement (138Kb) and the necessity for EMS
  22676.  memory may limit its usefulness for some developers. In contrast, Symdeb
  22677.  (43Kb), although lacking the full-screen user interface and access to
  22678.  local variables that are part of CodeView, will always be useful for certain
  22679.  Windows developers. Also, the copy of CodeView that we looked at did not
  22680.  have some of the internal Windows hooks that Symdeb has, such as the ability
  22681.  to dump the global heap, dump the free list, and so on. This is a minor
  22682.  issue, however, when compared with the many great advantages that CodeView's
  22683.  user interface provides.
  22684.  
  22685.  
  22686.  Figure 2:  The complete make file for compiling the Hello program, to be
  22687.             used with CodeView.
  22688.  
  22689.  #
  22690.  # Standard command line definitions
  22691.  #
  22692.  cp=cl -d -c -AS -Gsw -Od -Zpie
  22693.  #
  22694.  # Standard inference rules
  22695.  #
  22696.  #.c.obj:$(cp) $
  22697.  .c
  22698.  #
  22699.  # The C File List
  22700.  #
  22701.  
  22702.  hello.obj: hello.c hello.h
  22703.  
  22704.  hello.res: hello.rc hello.ico hello.h
  22705.     rc -r hello.rc
  22706.  
  22707.  hello.exe: hello.obj hello.res hello.def
  22708.     link4 hello/CO,/align:16,,slibw/NOE,hello.def
  22709.     rc hello.res
  22710.  
  22711.  
  22712.  Figure 3:  CodeView for Windows can be started in several ways, depending
  22713.             depending on what type of secondary monitor or terminal is in use.
  22714.  
  22715.  Command when using an MDA adapter:
  22716.  
  22717.    C> cvw /2 /L hello.exe \windows\win.com hello.exe
  22718.  
  22719.  Command when using an asynchronous terminal and null modem adapter:
  22720.  
  22721.    C> cvw /C=com1 /T /L hello.exe \windows\win.com hello.exe
  22722.  
  22723.  ████████████████████████████████████████████████████████████████████████████
  22724.  
  22725.  OS/2 Graphics Programming Interface: An Introduction to Coordinate Spaces
  22726.  
  22727.  Charles Petzold
  22728.  
  22729.  PIXELS.
  22730.          You can't draw with them.
  22731.          You can't draw without them.
  22732.  
  22733.  Pixels are the single greatest impediment to device-independent graphics
  22734.  programming. Part of the problem is that different output devices have
  22735.  different pixel resolutions. A 150-pixel-high image is 5 inches tall on a
  22736.  CGA display but only a half inch on a 300-dots-per-inch laser printer.
  22737.  Moreover, many display output devices have different horizontal and
  22738.  vertical resolutions. If you are working with pixels, you will have
  22739.  trouble drawing round circles and square squares.
  22740.  
  22741.  To avoid these problems, many high-level graphics programming languages
  22742.  give the programmer a means to draw in units other than pixels. The
  22743.  programmer specifies coordinate positions in terms of inches,
  22744.  millimeters, or other convenient units. The graphics system then converts
  22745.  these coordinates to pixels when drawing.
  22746.  
  22747.  The conversion of points from one coordinate system to another is called a
  22748.  "transform." At the very least, transforms should hide from the program
  22749.  the resolution of the output device and any differences in horizontal and
  22750.  vertical resolution. But transforms also play an important role in
  22751.  "modeling"─the construction of images from individual elements.
  22752.  Transforms provide the means to manipulate these elements before they are
  22753.  displayed.
  22754.  
  22755.  The GPI (Graphics Programming Interface) component of the OS/2
  22756.  Presentation Manager supports transforms both for working in units other
  22757.  than pixels and for more sophisticated modeling tasks. In fact, the GPI
  22758.  contains so many transform functions that the subject can be quite
  22759.  confusing to the GPI newcomer.
  22760.  
  22761.  This article discusses the GPI transforms that are supported under the
  22762.  "micro-presentation space" (or micro-PS). As I discussed in "The Graphics
  22763.  Programming Interface: A Guide to OS/2 Presentation Spaces," (MSJ, Vol.
  22764.  3 No. 3), the micro-PS supports only a subset of GPI functions. However,
  22765.  working with a subset can certainly be an advantage when learning a new
  22766.  topic because you can simply ignore everything not included in the
  22767.  subset.
  22768.  
  22769.  
  22770.  I Point, Therefore I Am
  22771.  
  22772.  When you draw with GPI functions, you specify a two-dimensional point
  22773.  using the POINTL structure. The POINTL structure contains two fields named
  22774.  x and y. GPI maps the point you specify in the POINTL structure to a point
  22775.  on the output device (which can be the screen, a printer, or a plotter).
  22776.  
  22777.  At first, this seems like simple analytic geometry. The points you use in
  22778.  GPI functions are simply points in a Cartesian coordinate system, as shown
  22779.  in Figure 1. We can represent a point in this coordinate space by the
  22780.  notation (x,y). The point (0,0) is the center where the horizontal and
  22781.  vertical axes meet.
  22782.  
  22783.  This is good for starters, but its simplicity is deceptive. Let's pose a
  22784.  few basic questions:
  22785.  
  22786.    ■  Where is the origin? That is, when you use a POINTL structure with
  22787.       the x and y fields both set to zero, where is that point mapped to on
  22788.       the output device?
  22789.  
  22790.    ■  How do units of x and y in the POINTL structure correspond to the
  22791.       natural pixel units of the output device? Do increasing values of x
  22792.       really go to the right? Do increasing values of y really go up?
  22793.  
  22794.    ■  Is it necessary for values of x to represent a position on the
  22795.       horizontal axis and for values of y to represent a position on the
  22796.       vertical axis? Can the entire coordinate system be rotated in some
  22797.       way to look like Figure 2?
  22798.  
  22799.    ■  Is it even necessary for the x and y axes to be at right angles to
  22800.       each other? Or can the coordinate system look like the one that is
  22801.       shown in Figure 3?
  22802.  
  22803.  The answers to these questions are: whatever you want them to be. Using
  22804.  GPI transforms, you control exactly how the points you specify in GPI
  22805.  functions are mapped to the output device.
  22806.  
  22807.  In fact, these four sets of questions are related to the four basic types
  22808.  of transforms:
  22809.  
  22810.    ■  Translation: Where the point (0,0) is mapped to on the output
  22811.       device.
  22812.  
  22813.    ■  Scaling: How units of x and y correspond to pixels.
  22814.  
  22815.    ■  Rotation: How the x and y axes are oriented.
  22816.  
  22817.    ■  Shear: Whether the two axes are at right angles to each other or
  22818.       not.
  22819.  
  22820.  We'll examine these four types of transforms in more detail later in this
  22821.  article.
  22822.  
  22823.  
  22824.  Visualizing Transforms
  22825.  
  22826.  One way of thinking about graphics transforms is to imagine the axes
  22827.  shown in Figures 1, 2, and 3 as being superimposed on an output device,
  22828.  such as the video display. The points you use in GPI functions specify a
  22829.  point in the coordinate space. That point corresponds to a particular
  22830.  pixel position on the screen.
  22831.  
  22832.  Using GPI transform functions, you can effectively shift the axes so that
  22833.  the origin is at a different location on the screen. You can stretch or
  22834.  compress an axis so that the units you use relate to pixels in different
  22835.  ways. You can rotate the axes so that objects you draw appear to be
  22836.  rotated. You can tilt one or both axes so that objects are distorted.
  22837.  
  22838.  But you'll find that this visual approach is not the best way to think
  22839.  about GPI transforms. It's relatively easy if you're dealing with only two
  22840.  coordinate systems (the coordinate space in which you draw and the
  22841.  natural coordinate space of the output device), but the micro-PS subset of
  22842.  GPI defines three additional coordinate systems between these two. You'll
  22843.  find that the visual approach is inadequate and that instead you must rely
  22844.  on formulas. These formulas govern how a point is mapped from one
  22845.  coordinate space to the next.
  22846.  
  22847.  
  22848.  Five Coordinate Spaces
  22849.  
  22850.  The five micro-PS coordinate spaces are shown in the table in Figure 4.
  22851.  The "Transform" column of the table shows the name of the transform that
  22852.  maps between each pair of coordinate spaces. The last column shows the GPI
  22853.  function that lets you set or change the transformation formula.
  22854.  
  22855.  The points you specify in GPI drawing functions are in world coordinate
  22856.  space. These points are eventually mapped to the output device, which is
  22857.  the media coordinate space.
  22858.  
  22859.  
  22860.  The Media and the Device
  22861.  
  22862.  The best place to start is at the bottom of Figure 4 with the windowing
  22863.  system transform. This is the easiest transform because it's one you can't
  22864.  control and therefore do not have to worry about. The windowing system
  22865.  transform is handled within the Presentation Manager.
  22866.  
  22867.  Media coordinate space is dependent on the hardware of the output device.
  22868.  The video display, for example, is organized in rows and columns of
  22869.  pixels. Each pixel is one unit in media space. For convenience, the
  22870.  origin-the point (0,0)-is defined to be the lower-left corner of the
  22871.  display. Increasing values of x go to the right; increasing values of y
  22872.  go up.
  22873.  
  22874.  But you never use media space coordinates in GPI functions. To draw on
  22875.  the display, you need a handle to a presentation space. The presentation
  22876.  space is always identified with a particular window. The window has its own
  22877.  coordinate system known as device space. The origin is at the lower-left
  22878.  corner of the window. Like media space, units are pixels. Increasing values
  22879.  of x go to the right; increasing values of y go up.
  22880.  
  22881.  The windowing system transform maps from device space to media space.
  22882.  This transform is based on the position of the window relative to the screen.
  22883.  pixels from the left side of the screen and 100 pixels from the bottom.
  22884.  The windowing system transformation formulas are:
  22885.  
  22886.    xmedia = xdevice + 50
  22887.    ymedia = ydevice + 100
  22888.  
  22889.  Thus, the point (0,0) in device space is mapped to the point (50,100) in
  22890.  media space. These are simple transformation formulas that effectively
  22891.  shift the origin of the coordinate space. This type of transform is called
  22892.  a "translation."
  22893.  
  22894.  
  22895.  The Presentation Page
  22896.  
  22897.  The presentation page (also known more simply as the page) is the
  22898.  coordinate space in which a picture is constructed for display. You can
  22899.  visualize the page as precisely that──a page of paper. On the screen, you
  22900.  view the whole page or a part of the page in a program's window.
  22901.  
  22902.  The units of the page can be pixels or other units of measure (such as
  22903.  inches or millimeters) or anything else that is convenient for the
  22904.  application. The lower-left corner of the page usually coincides with the
  22905.  lower-left corner of device space (the window), but it doesn't have to.
  22906.  
  22907.  The presentation page maps to device space through the device transform.
  22908.  This is the transform that allows a program to work in units other than
  22909.  pixels.
  22910.  
  22911.  When you create a presentation space by calling the function
  22912.  GpiCreatePS, the device transform is set for you. In the GpiCreatePS
  22913.  function, you specify the units of the presentation page (for example, by
  22914.  using the identifier PU_LOMETRIC for units of 0.1 millimeter or the
  22915.  identifier PU_LOENGLISH for units of 0.01 inch) and the size of the page
  22916.  in those units.
  22917.  
  22918.  The size of the page in page units is specified in a SIZEL structure that
  22919.  is then passed to GpiCreatePS. The SIZEL structure has two fields named
  22920.  cx and cy. For example, if you decide to use PU_LOENGLISH page units and
  22921.  you want the page to be 81/2 by 11 inches, set cx to 850 and cy to 1100.
  22922.  If you use 0 values in the SIZEL structure, the size of the page is set
  22923.  equal to the size of the output medium. For a window on the display, this
  22924.  is the size of the screen.
  22925.  
  22926.  The device transform is defined in terms of a RECTL (rectangle) structure.
  22927.  After you call GpiCreatePS to create a presentation space, you can obtain
  22928.  the device transform rectangle by calling
  22929.  
  22930.    GpiQueryPageViewport ( hps, &rcl) ;
  22931.  
  22932.  
  22933.  where rcl is a structure of type RECTL. You will find that the values are
  22934.  set as is shown in Figure 5.
  22935.  
  22936.  The device transform formulas, shown in Figure 6, are rather ugly. The
  22937.  rcl variable (a structure of type RECTL) is the device transform
  22938.  rectangle, and sizl (a structure of type SIZEL) contains the page size in
  22939.  page units.
  22940.  
  22941.  If the xLeft and yBottom fields of the RECTL structure are 0 (as they are
  22942.  when you first create the presentation space), then these two formulas
  22943.  reduce to those shown in Figure 7.
  22944.  
  22945.  This transform does exactly what we expect: it transforms the page units
  22946.  (which could be 0.1 millimeter or 0.01 inch) into pixels by multiplying them
  22947.  by a factor that relates those units to the pixel size. This type of
  22948.  transform is known as "scaling." The complete formula also allows for
  22949.  translation.
  22950.  
  22951.  The size of the page that you specify in the GpiCreatePS function does
  22952.  not affect the device transform. Whatever page size you choose, GPI will
  22953.  calculate a device transform appropriate to it. However, the page size
  22954.  affects clipping because the graphics field is initially set to the size of
  22955.  the page.
  22956.  
  22957.  If you create a presentation space using GpiCreatePS, you will probably
  22958.  want to leave the device transform alone. However, you can do some tricks
  22959.  with it, such as setting different scaling factors or translating the
  22960.  origin of the presentation page to someplace other than the lower-left
  22961.  corner of the window.
  22962.  
  22963.  For example, if you wish to put the origin in the middle of the window,
  22964.  first obtain the device transform rectangle by calling
  22965.  GpiQueryPageViewport, add half the width of the client window to the
  22966.  xLeft and xRight fields and half the height of the client window to the
  22967.  yBottom and yTop fields, and then set the new device transform by calling
  22968.  GpiSetPageViewport. You'll also need to change the graphics field
  22969.  clipping area to encompass the whole window in page units.
  22970.  
  22971.  Windows programmers should note that the function GpiSetPageViewport
  22972.  does the job of several Windows functions: SetViewportOrg,
  22973.  SetWindowOrg, and SetViewportExt, in addition to SetWindowExt.
  22974.  However, it is not quite as easy to use as the Windows functions.
  22975.  
  22976.  
  22977.  Cached Micro-PS
  22978.  
  22979.  If you use a cached micro-PS, the presentation page is always in units of
  22980.  pixels. You can use the device transform to change that. For example,
  22981.  suppose you want to draw in units of 0.01 inch. You first obtain a handle
  22982.  to the window device context:
  22983.  
  22984.    hdc = WinOpenWindowDC(hwnd) ;
  22985.  
  22986.  You then make four calls to DevQueryCaps to determine the dimensions of
  22987.  the screen in pixels and the number of pixels per meter:
  22988.  
  22989.    DevQueryCaps (hdc, CAPS_WIDTH, 1L, &xScreenPixels) ;
  22990.    DevQueryCaps (hdc, CAPS_HEIGHT, 1L, &yScreenPixels) ;
  22991.    DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION, 1L, &yPixelsPerMeter) ;
  22992.    DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &xPixelsPerMeter) ;
  22993.  
  22994.  Because the size of the cached micro-PS page is the size of the screen,
  22995.  you define the fields of a RECTL structure like this:
  22996.  
  22997.    rcl.xLeft  = 0 ;
  22998.    rcl.yBottom = 0 ;
  22999.    rcl.xRight =
  23000.       ((xScreenPixels - 1) * xPixelsPerMeter * 254 + 500000) / 1000000 ;
  23001.    rcl.yTop  =  ((yScreenPixels - 1) * yPixelsPerMeter * 254
  23002.       + 500000) / 1000000 ;
  23003.  
  23004.  The ratio 254/1,000,000 converts meters to 0.01 inch. The number 500,000
  23005.  is for rounding. Thus, the xRight and yTop fields are set to the width and
  23006.  height of the screen in 0.01 inch. You then call:
  23007.  
  23008.    GpiSetPageViewport (hps, &rcl) ;
  23009.  
  23010.  You also need to set the graphics field to the size of the window in
  23011.  page units. To obtain this, you can use the GpiConvert function to convert
  23012.  from device space to page coordinates:
  23013.  
  23014.    WinQueryWindowRect (hps, &rcl) ;
  23015.    GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 2L, (PPOINTL) &rcl) ;
  23016.    GpiSetGraphicsField (hps, &rcl) ;
  23017.  
  23018.  And you're done.
  23019.  
  23020.  Obviously the device transform is not easy to work with. It's easiest to
  23021.  create a micro-PS using GpiCreatePS and specify the page size and page
  23022.  units you want. That defines the device transform once and for all.
  23023.  
  23024.  
  23025.  Constructing the Picture
  23026.  
  23027.  If you don't use a transform, other than the device transform as it is
  23028.  set by GpiCreatePS, then everything you draw using GPI functions is in
  23029.  page units-the units you selected in the GpiCreatePS function. (Actually,
  23030.  there are some exceptions: GPI regions are always defined in device
  23031.  units, for example.) The origin is located in the lower-left corner of
  23032.  the window. Increasing values of x move to the right; increasing values of
  23033.  y move up.
  23034.  
  23035.  As you construct a picture on the presentation page, you also have
  23036.  available two additional transforms: the modeling transform and the
  23037.  default viewing transform. The points you use in GPI functions are in
  23038.  world coordinate space. The modeling transform converts those points to
  23039.  model coordinate space. The default viewing transform converts the points
  23040.  in model space to points in the presentation page space.
  23041.  
  23042.  For some simple purposes, the modeling and default viewing transforms are
  23043.  interchangeable. But a general rule to follow is this: the transforms
  23044.  closest to the hardware (that is, the transforms at the bottom of
  23045.  Figure 4) should be changed less frequently than the transforms furthest
  23046.  from the hardware. The simplest approach is to use the default viewing
  23047.  transform to apply to the whole picture and different modeling transforms
  23048.  to apply to pieces of the picture.
  23049.  
  23050.  Suppose you want to work with a coordinate system in which the center of
  23051.  the window is the origin. You use the default viewing transform to
  23052.  accomplish that. (The TRANSFRM program discussed later does exactly
  23053.  that.) Suppose you want to draw a picture in which a particular object
  23054.  appears in several different places. You can draw the object with
  23055.  different modeling transforms. The same points are passed to the GPI
  23056.  drawing functions, but the modeling transform causes those points to be
  23057.  transformed.
  23058.  
  23059.  
  23060.  Transformation Matrix
  23061.  
  23062.  Both the modeling transform and the default viewing transform are based
  23063.  on a standard two-dimensional "transformation matrix" common in many
  23064.  high-level graphics languages such as GKS (Graphical Kernel System).
  23065.  
  23066.  In GPI, you specify a transformation matrix as a structure of type
  23067.  MATRIXLF. (The LF part of the structure name stands for LONG and FIXED,
  23068.  the two data types used for the structure fields.) The MATRIXLF structure
  23069.  is defined in the PMGPI.H header file, like this:
  23070.  
  23071.    typedef struct _MATRIXLF
  23072.      {
  23073.      FIXED fxM11 ;
  23074.      FIXED fxM12 ;
  23075.      LONG lM13 ;
  23076.      FIXED fxM21 ;
  23077.      FIXED fxM22 ;
  23078.      LONG lM23 ;
  23079.      LONG lM31 ;
  23080.      LONG lM32 ;
  23081.      LONG lM33 ;
  23082.      }
  23083.      MATRIXLF ;
  23084.  
  23085.  At first glance, the jumble of numbers in the field names looks like a
  23086.  real mess. But it's actually quite simple. The nine fields of this
  23087.  structure are the nine elements of a 3-by-3 matrix:
  23088.  
  23089.    │ fxM11  fxM12  lM13 │
  23090.    │                    │
  23091.    │ fxM21  fxM22  lM23 │
  23092.    │                    │
  23093.    │ lM31   lM32   lM33 │
  23094.  
  23095.  The M in the field names stands for "matrix," and the numbers are simply
  23096.  the row and column positions of each field within the matrix.
  23097.  
  23098.  The letter l prefix on some of the fields stands for LONG. The fx prefix
  23099.  stands for FIXED. The FIXED data type is a 32-bit signed long integer that
  23100.  is interpreted by GPI as a 16-bit signed integer and a 16-bit unsigned
  23101.  fraction. The FIXED data type allows GPI to use fractions without
  23102.  resorting to time-consuming floating-point operations. Figure 8 shows
  23103.  various examples of FIXED values.
  23104.  
  23105.  In some of the following examples I use normal decimal point notation for
  23106.  FIXED numbers. Keep in mind that these are really 32-bit integers.
  23107.  
  23108.  You can convert a FIXED variable to a float or double variable by dividing
  23109.  by 65536.0:
  23110.  
  23111.    dNumber = (fxNumber / 65536.0) ;
  23112.  
  23113.  You can convert a float or double variable to FIXED by multiplying by
  23114.  65536 and casting the result:
  23115.  
  23116.    fxNumber = (FIXED) (65536 * dNumber) ;
  23117.  
  23118.  The transformation matrix is actually slightly simpler than the version
  23119.  shown above. When you use a MATRIXLF structure in the GPI transform
  23120.  functions, the lM13 and lM23 fields must be set equal to 0 and the lM33
  23121.  field must be set equal to 1:
  23122.  
  23123.  
  23124.    │ fxM11  fxM12  0 │
  23125.    │                 │
  23126.    │ fxM21  fxM22  0 │
  23127.    │                 │
  23128.    │ lM31   lM32   1 │
  23129.  
  23130.  GPI uses a matrix multiplication to convert a point (x,y) to a point
  23131.  (x',y'). First, GPI constructs a 1-by-3 matrix from the original point
  23132.  (x,y) by appending a 1. Then that matrix is multiplied by the 3-by-3
  23133.  transformation matrix. The resultant 1-by-3 matrix contains the points
  23134.  x', y', and 1 and is shown in Figure 9.
  23135.  
  23136.  If (x,y) is a point in world coordinate space, then GPI applies the
  23137.  modeling transform to determine the point (x',y') in model coordinate
  23138.  space. If (x,y) is a point in model coordinate space, then GPI applies the
  23139.  default viewing transform, and the resultant point (x',y') is in
  23140.  presentation page units.
  23141.  
  23142.  The transformation matrix can also be represented as a pair of formulas
  23143.  that describe the matrix multiplication:
  23144.  
  23145.    x' = (fxM11) x + (fxM21) y + lM31
  23146.    y' = (fxM12) x + (fxM22) y + lM32
  23147.  
  23148.  Note that the points x' and y' are functions of both x and y. This is not
  23149.  the case in the device transform and windowing system transform.
  23150.  
  23151.  For a new presentation space, the modeling and default viewing
  23152.  transformation matrices are both identity matrices. Only the fxM11, fxM22,
  23153.  and lM33 fields are set to 1. The other fields are set to 0:
  23154.  
  23155.    │ 1.0  0.0  0 │
  23156.    │             │
  23157.    │ 0.0  1.0  0 │
  23158.    │             │
  23159.    │ 0    0    1 │
  23160.  
  23161.  Thus, the transformation formulas look like this:
  23162.  
  23163.    x' = x
  23164.    y' = y
  23165.  
  23166.  
  23167.  The TRANSFRM Program
  23168.  
  23169.  The TRANSFRM program shown in Figure 10 allows you to experiment with the
  23170.  GPI transformation matrix.
  23171.  
  23172.  The micro-PS is created in ClientWndProc during the processing of the
  23173.  WM_CREATE message. It uses page units of PU_LOENGLISH, or 0.01 inch.
  23174.  During the WM_PAINT message, the window procedure draws a ruler and a
  23175.  stick figure enclosed in a box, as shown in Figure 11. The ruler has tick
  23176.  marks every inch (100 units). The figure is enclosed in a box that is 2
  23177.  inches high and 2 inches wide.
  23178.  
  23179.  TRANSFRM sets the default viewing transform while processing the WM_SIZE
  23180.  message. This sets the origin of model space to the center of the window.
  23181.  The ruler is always drawn with a default modeling transform, which is set
  23182.  during the WM_PAINT message with the following function call:
  23183.  
  23184.    GpiSetModelTransformMatrix ( hps, 0L, NULL, TRANSFORM_REPLACE) ;
  23185.  
  23186.  The figure is drawn with a modeling transform that you define in a dialog
  23187.  box (see Figure 12). This transform is set using the MATRIXLF structure
  23188.  named matlfModel:
  23189.  
  23190.    GpiSetModelTransformMatrix ( hps, 9L, &matlfModel, TRANSFORM_REPLACE) ;
  23191.  
  23192.  In all cases the figure is drawn from the DrawFigure function using the
  23193.  same array of POINTL structures. Different modeling transformation
  23194.  matrices, however, can change the appearance of the object.
  23195.  
  23196.  
  23197.  Translation
  23198.  
  23199.  Suppose you have in your program an array of POINTL structures that
  23200.  defines a 100-unit square with the bottom-left corner at point (0,0). You
  23201.  want to draw this square using these points, but you want the bottom-left
  23202.  corner to be positioned at point (100,200). This requires a form of
  23203.  transform known as translation.
  23204.  
  23205.  Nonzero values of the lM31 and lM32 elements in the transformation
  23206.  matrix cause an image to be shifted or translated from one place to
  23207.  another without any distortion of the image:
  23208.  
  23209.    │ 1.0   0.0   0 │
  23210.    │               │
  23211.    │ 0.0   1.0   0 │
  23212.    │               │
  23213.    │ lM31  lM32  1 │
  23214.  
  23215.  The transformation formulas are:
  23216.  
  23217.    x' = x + lM31
  23218.    y' = y + lM32
  23219.  
  23220.  The matrix translates the point (0,0) to the point (lM31,lM32). If the
  23221.  matrix represents the default viewing transform, then the point (0,0)
  23222.  in model coordinate space is the same as the point (lM31,lM32) in page
  23223.  coordinate space.
  23224.  
  23225.  TRANSFRM.C uses translation in order to set the model space origin to
  23226.  the center of the window. During the processing of the WM_SIZE message in
  23227.  the client window procedure, TRANSFRM sets the default viewing
  23228.  transformation matrix so that the lM31 field is half the width of the
  23229.  client window in page units and lM32 is half the height. Because the
  23230.  WM_SIZE message reports the size of a window in pixels, TRANSFRM must use
  23231.  the GpiConvert function to convert the center position from device space
  23232.  to page space.
  23233.  
  23234.  Changing the position of the figure relative to the ruler can be done by
  23235.  entering different values of lM31 and lM32 in the TRANSFRM dialog box.
  23236.  Figure 13 shows the result when lM31 equals 150 and lM32 equals ──50.
  23237.  
  23238.  The figure is shifted right by 150 units (1.5 inches) and down by 50 units
  23239.  (0.5 inch). As you can see, translation will always transform a square to
  23240.  another square. The object is simply moved to a different location on the
  23241.  output device.
  23242.  
  23243.  
  23244.  Scaling
  23245.  
  23246.  The fxM11 and fxM22 fields of the MATRIXLF structure are the "scaling"
  23247.  fields. If fxM11 is greater than 1.0, then the resultant image is
  23248.  expanded along the x axis. Values less than 1.0 cause the width of the
  23249.  image to be compressed. Similarly, fxM22 affects the height of the image.
  23250.  
  23251.  If all other fields of the matrix are set to their default values, the
  23252.  transformation matrix for scaling is
  23253.  
  23254.    │ fxM11  0.0    0 │
  23255.    │                 │
  23256.    │ 0.0    fxM22  0 │
  23257.    │                 │
  23258.    │ 0      0      1 │
  23259.  
  23260.  and the transformation formulas are:
  23261.  
  23262.    x' = (fxM11) x
  23263.    y' = (fxM22) y
  23264.  
  23265.  In general, scaling causes a square to be transformed into a rectangle.
  23266.  Figure 14 shows TRANSFRM with fxM11 set to 3.0 and fxM22 set to 0.5.\
  23267.  
  23268.  
  23269.  Reflection
  23270.  
  23271.  Reflection is a form of scaling that uses negative scaling factors.
  23272.  Negative values of the fxM11 and fxM12 fields cause the image to be
  23273.  flipped horizontally or vertically.
  23274.  
  23275.  For example, if you use this transformation matrix
  23276.  
  23277.    │ -1.0  0.0  0 │
  23278.    │              │
  23279.    │  0.0  1.0  0 │
  23280.    │              │
  23281.    │  0    0    1 │
  23282.  
  23283.  then the transformation formulas are:
  23284.  
  23285.    x' = -x
  23286.    y' = y
  23287.  
  23288.  This reflection flips the image around the vertical axis (see Figure 15).
  23289.  
  23290.  Similarly, this matrix
  23291.  
  23292.    │ 1.0   0.0  0 │
  23293.    │              │
  23294.    │ 0.0  -1.0  0 │
  23295.    │              │
  23296.    │ 0     0    1 │
  23297.  
  23298.  causes a reflection around the horizontal axis and turns the object upside
  23299.  down (Figure 16).
  23300.  
  23301.  
  23302.  Shear
  23303.  
  23304.  The shear transform effectively tilts one of the coordinate axes in such
  23305.  a way that it is not at right angles to the other axis. The resultant
  23306.  drawing is distorted: a square is mapped to a parallelogram.
  23307.  
  23308.  A nonzero value of fxM21 causes x-shear:
  23309.  
  23310.    │ 1.0    0.0  0 │
  23311.    │               │
  23312.    │ fxM21  1.0  0 │
  23313.    │               │
  23314.    │ 0      0    1 │
  23315.  
  23316.  The transformation formulas are:
  23317.  
  23318.    x' = x + (fxM21) y
  23319.    y' = y
  23320.  
  23321.  This shifts a point left or right by an amount dependent upon the distance
  23322.  between the point and the horizontal axis. Figure 17 shows the TRANSFRM
  23323.  screen when fxM21 is set to 1.0.
  23324.  
  23325.  Similarly, the fxM12 field causes y-shear. The transformation matrix
  23326.  is:
  23327.  
  23328.    │ 1.0  fxM12  0 │
  23329.    │               │
  23330.    │ 0.0  1.0    0 │
  23331.    │               │
  23332.    │ 0    0      1 │
  23333.  
  23334.  Figure 18 shows y-shear when fxM12 is set to 1.0.
  23335.  
  23336.  Shear sometimes suggests a three-dimensional view of an object. However,
  23337.  because a square is always mapped to a parallelogram, there is no real
  23338.  depth perspective.
  23339.  
  23340.  Now try this matrix, which combines y-shear and negative x-shear:
  23341.  
  23342.    │  1.0  1.0  0 │
  23343.    │              │
  23344.    │ -1.0  1.0  0 │
  23345.    │              │
  23346.    │  0    0    1 │
  23347.  
  23348.  The result appears in Figure 19, in which the image is rotated 45 degrees
  23349.  counterclockwise and enlarged somewhat. This suggests that a combination
  23350.  of scaling and shear can produce rotation, which is exactly what happens.
  23351.  
  23352.  
  23353.  Rotation
  23354.  
  23355.  From basic trigonometry we know that to rotate an image a degrees around
  23356.  the origin in a counterclockwise rotation requires the following
  23357.  formulas:
  23358.  
  23359.    x' = x * cos(a) ── y * sin(a)
  23360.    y' = x * sin(a) +
  23361.  
  23362.  These formulas fit very nicely into the transformation matrix:
  23363.  
  23364.    │  cos(a)   sin(a)   0 │
  23365.    │                      │
  23366.    │ ─sin(a)   cos(a)   0 │
  23367.    │                      │
  23368.    │  0        0        1 │
  23369.  
  23370.  For example, to rotate an image 45 degrees counterclockwise around the
  23371.  origin, use:
  23372.  
  23373.    │  0.707  0.707  0 │
  23374.    │                  │
  23375.    │ ─0.707  0.707  0 │
  23376.    │                  │
  23377.    │  0      0      1 │
  23378.  
  23379.  To rotate an image 90 degrees counterclockwise, use:
  23380.  
  23381.    │  0.0  1.0  0 │
  23382.    │              │
  23383.    │ -1.0  0.0  0 │
  23384.    │              │
  23385.    │  0    0    1 │
  23386.  
  23387.  You can experiment with rotation angles in TRANSFRM by selecting the
  23388.  "Angle..." button to invoke a second dialog box from the first. This is
  23389.  shown in Figure 20.
  23390.  
  23391.  A rotation of 112.5 degrees and a scaling factor of 2.5 is seen in
  23392.  Figure 21.
  23393.  
  23394.  
  23395.  Working Backwards
  23396.  
  23397.  The TRANSFRM program also allows you to derive a transformation matrix
  23398.  by selecting three corners of the box that encloses the stick figure. You
  23399.  select the lower-left corner by clicking with the mouse, the lower-right
  23400.  corner by clicking with the mouse while holding down the Shift key, and
  23401.  the upper-left corner by clicking with the mouse while holding down the
  23402.  Ctrl key.
  23403.  
  23404.  
  23405.  Matrix Multiplication
  23406.  
  23407.  One reason why matrices are used to represent graphics transforms is that
  23408.  the cumulative effect of two transforms is equivalent to the product of
  23409.  the two matrices.
  23410.  
  23411.  For example, suppose you want to first double the height of an object and
  23412.  then rotate the object 45 degrees counterclockwise. You would multiply
  23413.  the two matrices that describe these two transforms, as shown in
  23414.  Figure 22.
  23415.  
  23416.  In the resultant matrix, the element in the ith row and jth column is the
  23417.  summation of element-by-element products of the ith row of the first
  23418.  matrix and the jth column of the second matrix.
  23419.  
  23420.  Matrix multiplication is associative but not commutative. If you want to
  23421.  first rotate an object 45 degrees and then double the height, the
  23422.  composite transformation matrix would be calculated as shown in
  23423.  Figure 23.
  23424.  
  23425.  A combination of matrices is often used with rotation to choose the point
  23426.  around which the object is rotated. For example, suppose you have stored
  23427.  a series of points that describes an object. You want to rotate this
  23428.  object 90 degrees around the point (100,100). This is equivalent to
  23429.  translating the object by ──100 units on the x axis and ──100 units on the y
  23430.  axis, rotating the image around the origin, then translating it back 100
  23431.  units on the x and y axes. The three matrices that describe these
  23432.  transforms are multiplied together in sequence as shown in Figure 24.
  23433.  
  23434.  The result is:
  23435.  
  23436.    │  0.0  1.0  0 │
  23437.    │              │
  23438.    │ ─1.0  0.0  0 │
  23439.    │              │
  23440.    │  200  0    1 │
  23441.  
  23442.  This is the same as rotating the image 90 degrees and then translating
  23443.  it 200 units along the x axis.
  23444.  
  23445.  
  23446.  Combining Transforms
  23447.  
  23448.  You do not have to do the matrix multiplication in your program. GPI can
  23449.  do the multiplications for you by using the TRANSFORM_ADD and
  23450.  TRANSFORM_PREEMPT parameters to such functions as
  23451.  SetModelTransformMatrix and SetDefaultViewMatrix.
  23452.  
  23453.  The use of GPI matrix multiplication to combine three transforms is
  23454.  demonstrated in the REVOLVE program shown in Figure 25.
  23455.  
  23456.  This program does some rudimentary animation using the GPI transforms.
  23457.  It shows the stick figure drawn the same way that it was in the TRANSFRM
  23458.  program, but subjected to three transforms in the REVOLVE function
  23459.  SetModelTransform. The transforms are varied on each WM_TIMER
  23460.  message, so the figure moves and changes in shape. The three transforms
  23461.  are effectively multiplied together through the use of the parameter
  23462.  TRANSFORM_ADD when GpiSetModelTransformMatrix is called for the
  23463.  second and third matrices.
  23464.  
  23465.  The first transform scales the height of the figure by a factor ranging
  23466.  between ─1 and 1. (The factor is obtained from a sine function.) This
  23467.  gives the figure the appearance of falling head-over-heels. The second
  23468.  transform rotates the figure. The third transform scales the figure by
  23469.  the same factor on both the x and y axes. First, this scaling factor is
  23470.  decreased, which makes the figure look like it's falling away from you;
  23471.  then it is increased, which makes the figure look like it's falling toward
  23472.  you.
  23473.  
  23474.  
  23475.  Conclusion
  23476.  
  23477.  The key to creating sophisticated graphics applications is based on the
  23478.  ability to understand the interrelationships between coordinate spaces
  23479.  and the transforms required to move from one to another. The transforms
  23480.  themselves are controlled by transformation formulas and represented by
  23481.  transformation matrices. GPI provides a rich set of functions for handling
  23482.  transformations, allowing simple programs such as TRANSFRM and REVOLVE
  23483.  to create animated sequences. Although GPI was not designed for animation,
  23484.  it's nice to discover that it's features can be exploited for such
  23485.  purposes.
  23486.  
  23487.  
  23488.  Figure 1:  The Cartesian coordinate system that forms the basis of our
  23489.             intuitive notion of the GPI coordinate system. But it's not that
  23490.             simple.
  23491.  
  23492.                                        
  23493.                                        ∙
  23494.                                       +y
  23495.                                        ∙
  23496.                                        ∙
  23497.                                        ∙
  23498.                         ∙-x ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ +x∙ 
  23499.                                        ∙
  23500.                                        ∙
  23501.                                        ∙
  23502.                                       -y
  23503.                                        ∙
  23504.                                        
  23505.  
  23506.  
  23507.  Figure 2:  A rotated coordinate system.
  23508.  
  23509.                                                   
  23510.                              ∙                   ∙
  23511.                               +y              +x
  23512.                                  ∙           ∙
  23513.                                    ∙       ∙
  23514.                                      ∙   ∙
  23515.                                        ∙
  23516.                                      ∙   ∙
  23517.                                    ∙       ∙
  23518.                                  ∙           ∙
  23519.                               -x              -y
  23520.                              ∙                   ∙
  23521.                                                   
  23522.  
  23523.  
  23524.  Figure 3:  A coordinate system in which the two axes are not at right angles.
  23525.  
  23526.                                                    
  23527.                                                  ∙
  23528.                                               +y
  23529.                                              ∙
  23530.                                            ∙
  23531.                                          ∙
  23532.                         ∙-x ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ ∙ +x∙ 
  23533.                                      ∙
  23534.                                    ∙
  23535.                                  ∙
  23536.                               -y
  23537.                              ∙
  23538.                            
  23539.  
  23540.  
  23541.  Figure 4:  The five coordinate spaces in the micro-PS subset of GPI.
  23542.             Transforms map from one coordinate space to the next.
  23543.  
  23544. ╓┌────────────┌──────┌────────────┌───────────────┌──────────────────────────╖
  23545.   Coordinate  Units  Transform    Types of        GPI Function to
  23546.   Space                           Transformation  Set the Transform
  23547.  ┌─────────────────────┐
  23548.  │World       Arbitrary│
  23549.  └───────────────┬─────┘
  23550.                  │  ┌───────────────────────────────────────────────────────┐
  23551.                  └─│Modeling     Translation                               │
  23552.                     │             Scaling         GpiSetModelTransformMatrix│
  23553.                     │             Rotation                                  │
  23554.                     │             Shear                                     │
  23555.                     └─────┬─────────────────────────────────────────────────┘
  23556.  ┌─────────────────────┐  │
  23557.  │Model       Arbitrary│─┘
  23558.  └───────────────┬─────┘
  23559.                  │  ┌───────────────────────────────────────────────────────┐
  23560.                  └─│Default      Translation                               │
  23561.                     │Viewing      Scaling         GpiSetModelTransformMatrix│
  23562.                     │             Rotation                                  │
  23563.                     │             Shear                                     │
  23564.   Coordinate  Units  Transform    Types of        GPI Function to
  23565.   Space                           Transformation  Set the Transform
  23566.                    │             Shear                                     │
  23567.                     └─────┬─────────────────────────────────────────────────┘
  23568.  ┌─────────────────────┐  │
  23569.  │Page        Metrics, │─┘
  23570.  │           Pixels, or│
  23571.  │            arbitrary│
  23572.  └───────────────┬─────┘
  23573.                  │  ┌──────────────────────────────────────────────────────┐
  23574.                  └─│Device       Translation     GpiCreatePS and          │
  23575.                     │             Scaling         GpiSetPageViewport       │
  23576.                     └─────┬────────────────────────────────────────────────┘
  23577.  ┌─────────────────────┐  │
  23578.  │ Device     Pixels   │─┘
  23579.  │(window)             │
  23580.  └───────────────┬─────┘
  23581.                  │  ┌──────────────────────────────────────────────────────┐
  23582.                  └─│Windowing    Translation     (None)                   │
  23583.                     │System                                                │
  23584.                     └─────┬────────────────────────────────────────────────┘
  23585.   Coordinate  Units  Transform    Types of        GPI Function to
  23586.   Space                           Transformation  Set the Transform
  23587.                    └─────┬────────────────────────────────────────────────┘
  23588.  ┌─────────────────────┐  │
  23589.  │ Media      Pixels   │─┘
  23590.  │(screen)             │
  23591.  └─────────────────────┘
  23592.  
  23593.  
  23594.  Figure 5:  Initial values of the device transform rectangle after creating a
  23595.             presentation space.
  23596.  
  23597.  Device Transform
  23598.  RECTL Field         Initial Value
  23599.  
  23600.  rcl.xLeft           0
  23601.  rcl.yBottom         0
  23602.  rcl.xRight          Page width in pixels  ──1
  23603.  rcl.yTop            Page height in pixels ──1
  23604.  
  23605.  
  23606.  Figure 6:  Device Transformation Formulas
  23607.  
  23608.                             rcl.xRight - rcl.xLeft + 1
  23609.       x         = x      * ────────────────────────────  + rcl.xLeft
  23610.        device      page             sizl.cx
  23611.  
  23612.                             rcl.yTop - rcl.yBottom + 1
  23613.       y         = y      * ────────────────────────────  + rcl.yBottom
  23614.        device      page             sizl.cy
  23615.  
  23616.  
  23617.  Figure 7:  Reduced Device Transformation Formulas
  23618.  
  23619.                             Page width in pixels
  23620.       x        = x      * ────────────────────────
  23621.        device     page    Page width in page units
  23622.  
  23623.                            Page height in pixels
  23624.       y        = y      * ─────────────────────────
  23625.        device     page    Page height in page units
  23626.  
  23627.  
  23628.  Figure 8:  Examples of FIXED Values
  23629.  
  23630.  FIXED Value    Interpretation
  23631.  
  23632.         0x1     1/65536
  23633.      0x2000         1/8
  23634.     0x10000           1
  23635.     0xAC000      10 3/4
  23636.  0xFFF8F000     ─7 1/16
  23637.  
  23638.  
  23639.  Figure 9:  GPI Uses Matrix Multiplication to Convert Points
  23640.  
  23641.                     │ fxM11  fxM12  0 │
  23642.                     │                 │
  23643.                     │                 │
  23644.     │ x  y  1 │  *  │ fxM21  fxM22  0 │  =  │ x'  y'  1 │
  23645.                     │                 │
  23646.                     │                 │
  23647.                     │  lM31   lM32  1 │
  23648.  
  23649.  
  23650.  Figure 10:  Source Code for the TRANSFRM Program
  23651.  
  23652.  TRANSFRM Make File
  23653.  
  23654.  #--------------------
  23655.  # TRANSFRM make file
  23656.  #--------------------
  23657.  
  23658.  transfrm.obj : transfrm.c transfrm.h
  23659.       cl -c -G2sw -W3 -Zp transfrm.c
  23660.  
  23661.  transfrm.res : transfrm.rc transfrm.h
  23662.       rc -r transfrm
  23663.  
  23664.  transfrm.exe : transfrm.obj transfrm.def transfrm.res
  23665.       link transfrm, /align:16, NUL, os2, transfrm
  23666.       rc transfrm.res
  23667.  
  23668.  TRANSFRM.C Source Code
  23669.  
  23670.  /*-------------------------------------------
  23671.     TRANSFRM.C -- Demonstrates GPI Transforms
  23672.    -------------------------------------------*/
  23673.  
  23674.  #define INCL_WIN
  23675.  #define INCL_GPI
  23676.  
  23677.  #include <os2.h