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

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  Microsoft Systems Journal Volume 4
  2.  
  3.  ────────────────────────────────────────────────────────────────────────────
  4.  
  5.  Volume 4 - Number 1
  6.  
  7.  ────────────────────────────────────────────────────────────────────────────
  8.  
  9.  
  10.  Quotron Uses Windows to Develop New Market Analysis Tools for Real-Time Data
  11.  
  12.  ───────────────────────────────────────────────────────────────────────────
  13.  Also see the related article:
  14.    Utilizing the Object Oriented-Nature of Windows
  15.  ───────────────────────────────────────────────────────────────────────────
  16.  Tony Rizzo and Karen Strauss
  17.  
  18.  Traders know that making or losing a million dollars often depends on only
  19.  one thing──information. This goes a long way in explaining that "Wall
  20.  Street data obsession"──the drive to be the first to get the latest market
  21.  information.
  22.  
  23.  In 1851, Reuters delivered market information by carrier pigeon. On Wall
  24.  Street traders rushed stock quotes written on pads of paper to brokerages
  25.  via messengers called pad-shovers. In 1867 the ticker tape machine was
  26.  patented and the market underwent a revolution. Quotes became available on a
  27.  tick-by-tick basis; when a stock moved, a mark appeared on ticker tape
  28.  machines in every brokerage house on the Street.
  29.  
  30.  The ticker tape evolved into the electronic wall ticker, which flashes a
  31.  continuous stream of quotes. Electronic data collection arrived on traders'
  32.  desks in 1960 in the guise of the Quotron(R) I. QUOTEVUE(R) followed in
  33.  1967, the first machine to provide quotes via a screen display.
  34.  
  35.  Before long a brokerage's stature came to be measured by the presence or
  36.  absence of Quotron terminals on the desks of its brokers. These terminals
  37.  provided up-to-the-minute quotations──and functioned as a desktop
  38.  electronic ticker tape. Quotron terminals eventually became the status
  39.  quo.
  40.  
  41.  
  42.  Quotron 1000
  43.  
  44.  In the financial world a firm's competitive edge depends on its ability to
  45.  gather data and, even more important, on its ability to quickly analyze and
  46.  act on it. The latest technology available for this task is the Quotron
  47.  1000(TM) (Q1000). The Q1000 is a UNIX(R)-based system of networked
  48.  minicomputers and software. Quotron provides a large package of financial
  49.  services and international networking capabilities, most of which are
  50.  based on the Q1000.
  51.  
  52.  Quotron computer systems are located in regional control centers (RCCs) that
  53.  are found throughout the United States, Europe, Japan, and elsewhere.
  54.  Workstations consist of dumb terminals and personal computers
  55.  connected to the Quotron computers through various communications
  56.  networks (see Figure 1). The majority of workstations within the Quotron
  57.  system are dumb terminals, but personal computers are making major inroads,
  58.  slowly taking over the desktops of traders and brokers. Quotron offers a
  59.  package called the PC1000, which lets a PC act as a workstation on the
  60.  Quotron network.
  61.  
  62.  As the amount of financial information available to the broker grows, it
  63.  becomes vital to provide better ways to manage data flow. Personal computers
  64.  are an ideal medium for doing this. First, PCs easily communicate with
  65.  networks such as that offered by Quotron. Second, real-time data can easily
  66.  be collected and off-loaded onto a PC. Once data is locally available,
  67.  users can run it through any analysis and charting package.
  68.  
  69.  But even this level of sophistication cannot match the newest wave of
  70.  technology. The simple ability to download data onto a PC, even from a real-
  71.  time data feed, is no longer enough. Now brokers must analyze on a tick-by-
  72.  tick basis, using data from both the domestic and foreign markets at the
  73.  moment the data becomes available.
  74.  
  75.  At the same time, the old character-based interfaces of dumb terminals and
  76.  PCs can no longer provide a useful window into this data stream. They are
  77.  giving way to exciting new graphical interfaces. A graphical interface
  78.  provides powerful new ways of viewing the constant streams of data that
  79.  confront users. Working at the leading edge of graphical technology,
  80.  Quotron has announced a new set of three PC-based applications designed to
  81.  function in the immediacy of such an environment.
  82.  
  83.  
  84.  Quotron Open Windows
  85.  
  86.  These applications are called Quotron Open Windows, and they work under the
  87.  Microsoft(R) Windows operating environment. They were designed and built in
  88.  a joint effort with Inmark Development Corporation, a firm experienced in
  89.  developing financial applications.
  90.  
  91.  QuotTerm(TM) is a terminal emulation program that duplicates the standard
  92.  Q1000 terminal. It provides the additional functionality of Windows.
  93.  QuotTerm uses a proprietary line to the Q1000.
  94.  
  95.  QuotData(TM) is an application that allows users to collect and download
  96.  data from a standard Q1000 data feed. It provides a means of building a
  97.  user-defined database, and lets a user track up to 2500 securities on a
  98.  tick-by-tick basis. QuotData maintains the database while running in either
  99.  the foreground or the background.
  100.  
  101.  Via Windows' Dynamic Data Exchange (DDE) protocol QuotData acts as a server
  102.  for any Windows application, and updates these applications on a real time,
  103.  tick-by-tick basis. QuotData also builds a historical database at market
  104.  close, made up of daily opening, high, low, and closing prices of each item
  105.  in the database. In addition, a historical database containing 10 years of
  106.  data is being added to the Quotron network. All of this will be available to
  107.  applications via DDE.
  108.  
  109.  QuotChart(TM) is a real-time charting and analysis tool. QuotChart gets its
  110.  data from QuotData and provides an extensive set of sophisticated charts
  111.  that reflect tick-by-tick changes in the underlying financial data.
  112.  
  113.  Quotron Open Windows requires either an 80286 or 80386 processor, at least
  114.  2Mb of RAM, a hard disk drive, one serial port, an EGA, VGA, or higher
  115.  resolution graphics adapter that supports Microsoft Windows, a bus mouse,
  116.  and a Q1000 port.
  117.  
  118.  
  119.  Speaking of Windows...
  120.  
  121.  Quotron Open Windows was created by Clifford P. Ribaudo, Inmark's President,
  122.  and Mark Anders, its Executive Vice President. In 1985 they built and
  123.  marketed Market Maker, a financial analysis and charting tool. Market Maker
  124.  is character-based and served as the model for development of the new
  125.  Quotron products. "A lot of ideas for Quotron Open Windows came from this
  126.  one. We learned about what to do and what not to do," says Ribaudo.
  127.  
  128.  After completing Market Maker, Ribaudo and Anders began searching for a new
  129.  programming environment. First, they wanted multitasking support on DOS-
  130.  based machines. Second, they wanted better graphical support for their
  131.  charting tools. "We explored TopView(R), DesqView, GSS(R), CGI; but none of
  132.  these really suited our needs," says Anders. The environments that supplied
  133.  multitasking were all character-based. The graphics packages that were
  134.  available in 1985 were built on standards such as CGI and GSS and were
  135.  generally not very robust.
  136.  
  137.  Then, according to Anders, "We booted up Windows, which we'd gotten through
  138.  a $25 offer that came with a Quadram EGA card we had purchased. We found
  139.  that we were looking at a unique environment that really seemed to meet our
  140.  needs." A quick call to Microsoft for more details led to the discovery of
  141.  the Windows Software Developer's Toolkit (SDK).
  142.  
  143.  "Windows had a graphical user interface that was part of a real multitasking
  144.  system," says Anders. "There were pull-down menus, support for a mouse, and
  145.  multitasking." Says Ribaudo, "It seemed to us an excellent model for
  146.  creating interactive applications.
  147.  
  148.  "One of the best things about Windows was that suddenly we found ourselves
  149.  within an environment that provided many of the tools we needed──dialog and
  150.  list boxes, check boxes and radio buttons──already implemented as part of
  151.  the environment. When we developed Market Maker, the character-based
  152.  interface meant that we ourselves had to carefully calculate and hard
  153.  code the locations of boxes, menus, and so on. Suddenly we no longer had to
  154.  pinpoint and track the location of every object in our application."
  155.  
  156.  Adds Anders, "One major advantage of Windows is that it really forced us to
  157.  take a careful look at our user interface. Windows really helped us to
  158.  organize and maintain a consistent interface. After some extensive
  159.  testing, Windows clearly emerged as the programming platform of choice."
  160.  
  161.  
  162.  Windows 2.x
  163.  
  164.  Both Ribaudo and Anders were glad to see Windows 2.x released. In addition
  165.  to overlapping windows and a cleaner user interface, it had improved
  166.  documentation. This aided their learning process. However, says Anders,
  167.  there is no escaping the fact that "there is a steep learning curve. It was
  168.  a struggle. I had no experience on other windowing and event-driven
  169.  environments to make it easier. It was the first time I had seen anything
  170.  like it. Now, however, it feels as if I was always comfortable with it. At
  171.  the time it seemed like I would spend weeks at a time stuck on one thing."
  172.  
  173.  "Control flow and message passing was the hardest to sort out. Windows has a
  174.  real personality of its own," adds Ribaudo. The two note, however, that
  175.  the difficulty of learning Windows is often overstated and that it didn't
  176.  take them that long to get the hang of it.
  177.  
  178.  Once they decided to build the applications under Windows, they set about
  179.  adapting their Market Maker technology. This eventually led to Quotron
  180.  Open Windows.
  181.  
  182.  
  183.  QuotTerm
  184.  
  185.  The Quotron dumb terminal and PC1000 software do a great job of providing
  186.  analysts with the basic data needed for even the most complex analysis. The
  187.  first challenge Inmark and Quotron faced was to come up with a good reason
  188.  why a company should replace the dumb terminal or PC running the PC1000
  189.  software with a Windows product.
  190.  
  191.  Quotron and Inmark felt they had to fully emulate the standard Quotron
  192.  screen of both the PC1000 software and the dumb terminal. At first glance it
  193.  seems a step back to want to turn the PC into a dumb terminal. But the key
  194.  issue was to give the analyst a safety cushion. Complete emulation of the
  195.  older environment would let any person familiar with the standard Quotron
  196.  terminal quickly use a PC equipped with QuotTerm. "The dumb terminal had to
  197.  be supported or we wouldn't be able to move people off of it onto a PC,"
  198.  says Ribaudo. "That, and the availability of charting and analysis tools,
  199.  and a personalized historical database, looked to be enough to convince
  200.  any broker to switch."
  201.  
  202.  QuotTerm therefore provides this total emulation, down to the same screen
  203.  locations and keystrokes for each service (see Figures 2 and 3). The
  204.  QuotTerm application links directly to a Q1000 computer, and operates
  205.  independently of the other available tools. But since QuotTerm works under
  206.  Windows there is much more functionality. The screen colors are all
  207.  modifiable, there is complete device-independent printer support, there is
  208.  the ability to selectively modify information-field blink rates, as well as
  209.  the ability to move and resize the QuotTerm window. With the mouse the user
  210.  can click on symbols displayed on the screen in order to retrieve full
  211.  quotes, headlines, and stories. And a user can run other Windows
  212.  applications while QuotTerm is active.
  213.  
  214.  
  215.  QuotData
  216.  
  217.  QuotData communicates directly with the Q1000. It monitors real-time data
  218.  and updates historical files. QuotData talks with other applications via a
  219.  proprietary link or DDE. Basically a communications/database front end
  220.  for the Q1000 system, it can be considered the hub around which all other
  221.  applications work. QuotData connects to the Q1000 system and requests
  222.  real-time updates on a symbol-by-symbol basis using the Quotron micro-sdf
  223.  (selective data feed) capability. Micro-sdf provides a proprietary
  224.  protocol for the transmission of real-time price updates from the Quotron
  225.  network through asynchronous lines to other computers. This protocol is
  226.  implemented by two processes, called microread and microwrite, which run on
  227.  the Quotron 1000.
  228.  
  229.  Microread blocks reading from the serial port that is awaiting requests
  230.  from the PC. When the port gets a request, the system forwards it to the
  231.  selective data feed driver, via a UNIX message queue. Seldf then requests
  232.  price updates from the network and transmits each message to a microwrite
  233.  over another message queue. Microwrite puts the data in packets and
  234.  transmits it to the PC.
  235.  
  236.  Windows dialog boxes make it simple for the user to make selections,
  237.  add/delete symbols, set the history parameters, and establish communications
  238.  links. Users can define a symbol list and indicate if the symbol has
  239.  associated volume and if it trades on any exchange with after-market hours.
  240.  The history function lets users specify the amount, in days, of historical
  241.  data they wish to store and save on disk.
  242.  
  243.  QuotData provides data to other applications running under Windows via two
  244.  types of interapplication transfers. The first is a proprietary data channel
  245.  that Inmark uses to talk to other Quotron/Inmark applications such as
  246.  QuotChart. This channel is basically a shared memory queue that the server
  247.  writes to and that any Quotron/Inmark application needing the data can read.
  248.  Available memory is the only limitation to the number of these conversations
  249.  that can take place at once.
  250.  
  251.  Second, and perhaps even more important, QuotData fully supports the
  252.  standard Windows DDE protocol. QuotData can carry on multiple DDE sessions
  253.  with any Windows application that supports DDE and wants to be "ADVISED"
  254.  with real-time market data. Ribaudo and Anders will build full DDE support
  255.  into all of their future products. Says Ribaudo, "What is important, and
  256.  what has excited developers who have seen QuotData, is that because of DDE
  257.  they will be able to develop Windows-based applications to work directly
  258.  with QuotData and need never worry about external connections to the Q1000
  259.  itself." Refer to Figure 4 for a high-level overview of the data links
  260.  supported.
  261.  
  262.  Communication through DDE is relatively simple. QuotData will support Text,
  263.  Comma Separated Values (CSV), and BIFF format. It will return four different
  264.  types of information, as shown in Figure 5. The information types shown in
  265.  Figure 6 will be supported in the near future.
  266.  
  267.  Microsoft Excel is a good example of a program that was built to work with
  268.  DDE. In Microsoft Excel, the user builds a spreadsheet that will create
  269.  whatever report he/she may need. Then it links to QuotData through DDE and
  270.  gets either historical data or tick-by-tick updates as required.
  271.  
  272.  The DDE data that an application needs to know in order to talk with
  273.  QuotData is shown in Figure 7. In the spreadsheet, every cell needing data
  274.  would contain a statement such as that shown in Figure 8A, where =INDEX is
  275.  the Microsoft Excel function needed to establish the link with QuotData, and
  276.  row# is a value ranging from 1 to 4, indicating what type of data it needs
  277.  (see Figure 5). Column# is always 1. For instance, if the line shown in
  278.  Figure 8B was defined in a spreadsheet cell, it would return the last sale
  279.  price of the security represented by the symbol AAPL (see Figure 9).
  280.  
  281.  
  282.  QuotChart
  283.  
  284.  QuotChart offers an extensive set of sophisticated charting and technical
  285.  analysis tools. These tools operate on the real-time data supplied by
  286.  QuotData via the proprietary channel mentioned above.
  287.  
  288.  QuotChart makes available all the major technical analysis studies with a
  289.  wide variety of charts (see Figure 10). Additional studies can be performed
  290.  and overlays created using tools such as Gann angles, cycle rule, Fibonacci
  291.  rule, moving averages, and trend lines. A tool called the vertical wand
  292.  lets the analyst select different "vertical periods" (sections) of a chart.
  293.  Once the analyst outlines a section (see Figure 11), he/she can do things
  294.  such as zoom in on that section.
  295.  
  296.  Aside from its graphics capabilities, one of the things that sets Windows
  297.  apart from the other environments Ribaudo and Anders considered is the
  298.  object-oriented nature of the Windows message-based architecture. As
  299.  Anders puts it, "Under Windows what you really have are a collection of
  300.  objects that communicate with each other through an established messaging
  301.  system-messages are relayed from object to object. This is a really useful
  302.  concept and supplies us with an elegant solution to some difficult design
  303.  problems."
  304.  
  305.  The real problem lies in being able to handle a constant, and constantly
  306.  changing, stream of data quickly and efficiently. Further, there is the need
  307.  to apply different types of analysis to this changing data. If a user wants
  308.  a moving average on one chart, and a stochastic on another, the system must
  309.  be able to deal not only with the data but with the underlying
  310.  mathematical formulas and relationships as well. As Ribaudo says, "We
  311.  need to be able to provide a means to extend and modify the analytics on the
  312.  fly."
  313.  
  314.  By using a special object-oriented language that they themselves developed,
  315.  and which works under Windows, Ribaudo and Anders solved this problem. Their
  316.  language consists of mathematical objects, which process streams of market
  317.  data that arrive as messages and are then quickly "relayed from object to
  318.  object."
  319.  
  320.  The processing structures in QuotChart are specified so that they look like
  321.  a combination of a macro language and standard mathematical equations.
  322.  Equation strings are specified at run time and built "below the surface,"
  323.  in response to selections that the user enters into dialog boxes. This
  324.  facilitates the rapid development of new analytic features. Says Ribaudo,
  325.  "To create a new type of analysis we need only create the new `object
  326.  primitives' in C code and add them to the `instruction set.' The primitives
  327.  are then combined in the language to create the new market study." For a
  328.  detailed technical description of this language, see the accompanying
  329.  sidebar, "Utilizing the Object-Oriented Nature of Windows."
  330.  
  331.  
  332.  Pages and Templates
  333.  
  334.  QuotChart also provides two other very useful features: pages and templates.
  335.  These permit saving of frequently used chart configurations. Pages let users
  336.  predefine and store every last detail of a chart or series of charts,
  337.  including all relevant data. The charts on a page all use the same "time
  338.  axis." A template defines the structure of a page and doesn't contain
  339.  specific references to data, although symbols can be saved if desired. A
  340.  template brings up a chart or collection of charts that perhaps define some
  341.  standard analysis that's applicable to different portfolios. It can be
  342.  saved, assigned to function keys, and called up at any time and used with
  343.  any portfolio.
  344.  
  345.  With pages, the analyst simply calls up a page and the predefined charts
  346.  pop up, are linked to the server and updated to reflect the most current
  347.  market information. Up to four charts can be saved per page, and two pages
  348.  (up to eight charts) can be displayed on the screen at the same time. An
  349.  enormous amount of information can be viewed simultaneously. Figures 12
  350.  and 13 show the page abilities of QuotChart.
  351.  
  352.  According to Ribaudo, the Windows environment makes all of this charting
  353.  activity relatively simple. Windows itself takes care of all the resizing
  354.  issues. "If a chart's size changes, Windows handles most of the display
  355.  details, so that creating tools such as a zoom feature for particular areas
  356.  of a given chart is quite simple. Windows makes the difference." Compare the
  357.  relatively static charts of Market Maker shown in Figure 14 with the
  358.  charting capabilities of QuotChart, shown in Figure 15.
  359.  
  360.  
  361.  What's Next?
  362.  
  363.  Both Anders and Ribaudo are committed to the Windows 2.x environment. Says
  364.  Anders, "You don't spend two and a half years coding a project under a
  365.  graphical user interface if you don't like the interface or the coding
  366.  methods." After working on Quotron Open Windows, a major new product by any
  367.  measure, they are both glad to finally see a "real" product.
  368.  
  369.  It is clear to Anders and Ribaudo that graphical interfaces are the key to
  370.  the next generation of software. When asked what their future directions
  371.  might be, their answer was quick and to the point. "As soon as this gets out
  372.  the door and the planned enhancements are in place, we are going to begin
  373.  looking at the port to OS/2 and Presentation Manager.
  374.  
  375.  
  376.  Figure 1:  An overview of the Quotron Network
  377.  
  378.                              ┌───────────┐
  379.                      ┌─X.25──┤ Additonal │
  380.       ▓▓▒▒░░░░▒▒▓▓   │       │ Databases │
  381.       ▓▓Uni-Link▓▓───┘       └───────────┘
  382.       ▓▓Network ▓▓───┐
  383.       ▓▓▒▒░░░░▒▒▓▓   │ Other   ┌───────────┐
  384.            │         └Protocols┤ Other     │
  385.     Quotron's Paged            │ Databases │
  386.         Format                 └───────────┘
  387.      ┌─────┴─────┐
  388.      │ Quotron's │Request for historical data
  389.      │   CARS    │packed into the page format for
  390.      │   Host    │transmission to CARS by Q1000
  391.      └─────┬─────┘
  392.            │                ▓▓▓▒▒░░░▒▒▓▓▓
  393.            └────────────────▓▓▓▒▒░░░▒▒▓▓▓
  394.                             ▓▓Quotron's▓▓
  395.                             ▓▓Network  ▓▓
  396.     Paged response          ▓▓▓▒▒░░░▒▒▓▓▓
  397.     unpacked by Q1000       ▓▓▓▒▒░░░▒▒▓▓▓
  398.      ┌────────┐              │    │    │
  399.      │ Remote │        ┌─────┘    │    └───────────────────────┐
  400.      │ Users  │        │          └────────────────┐           │
  401.      └──────┬─┘    ┌───┴───┐                   ┌───┴───┐   ┌───┴───┐
  402.             │      │       │ ┌─────────────┐   │       │   │       │
  403.             └──────┤ Q1000 ├─┤ Peripherals │   │ Q1000 │   │ Q1000 │
  404.                    │       │ └─────────────┘   │       │   │       │
  405.                    └──┬─┬──┘                   └┬──┬──┬┘   └───┬───┘
  406.               ┌───────┘ │             ┌─────────┘  │  └──┐     └────────┐
  407.               │         │             │        ┌───┘     │              │
  408.          ┌────┴───┐┌────┴───┐    ┌────┴───┐┌───┴────┐┌───┴────┐    ┌────┴───┐
  409.          │   PC   ││   PC   │    │   PC   ││  SUN   ││  SUN   │    │   PC   │
  410.          │        ││        │    │        ││        ││        │    │        │
  411.          └────────┘└────────┘    └────────┘└────────┘└────────┘    └────────┘
  412.           A request for historical data generated by an application running
  413.           on then PC, SUN(R) or Q1000 uses a standardized request format
  414.  
  415.  
  416.  Figure 4:  A high-level overview of current Quotron Open Windows Data Links.
  417.  
  418.  ┌──────────┐          ┌──────────────────────────────────┐
  419.  │ Regional │          │   UNIX-based Q1000               │
  420.  │ Control  │────────│  ┌────────────────┐  ┌────────┐  │  ┌───────────┐
  421.  │ Center   │          │  │Logical Database├─│Terminal├────│ Quotron   │
  422.  │ (RCC)    │          │  └───────────┬───┘  │        │  │  │ Terminals │
  423.  └──────────┘          │  ┌──┴───┐  ┌─────┐  │Support ├────┐└───────────┘
  424.                        │  │Micro-│  │Micro-│  └────────┘  │ │
  425.                        │  │ read │  │write │              │ │    ┌───────┐
  426.                        │  └─────┘  └──┬───┘              │ │    │  PC   │
  427.                        │     │         │                  │ │    │running│
  428.                        │     └──█████─┘                  │ │    │Windows│
  429.                        └────────▀▀▀▀─────────────────────┘ │    │2.x or │
  430.           ┌───────────────────────│─────────────────────────│────┤higher │
  431.           │          ┌────────────┘                         │    └──────┬┘
  432.           │ ┌────────────────┐                     ┌────────────────┐ │
  433.           │ │■──────────────│                     │■──────────────│ │
  434.           │ │                 │                     │                 │ │
  435.           │ │    QuotData     │                     │    QuotTerm     │ │
  436.           │ │                 │                     │                 │ │
  437.  Via a    │ └──────────┬─┬───┘                     └─────────────────┘ │
  438.  Proprietary         │  │ └────────────DDE─────────────┐                │
  439.  Data Link───────────┤  └────DDE───────┐               │                │
  440.  (DDE will           │                 │      ┌────────────────┐       │
  441.  be supported        │         ┌────────────────┐────────────│       │
  442.  in the next    ┌────────────────┐────────────│ Other        │       │
  443.  release) │     │■──────────────│ Microsoft    │ Windows      │       │
  444.           │     │                 │   Excel      │ Applications │       │
  445.           │     │   QuotChart     │              │──────────────┘       │
  446.           │     │                 │──────────────┘                      │
  447.           │     └─────────────────┘                                     │
  448.           └─────────────────────────────────────────────────────────────┘
  449.  
  450.  
  451.  Figure 5:  QuotData returns information to Microsoft Excel.
  452.  
  453.  Last Price
  454.  Net Change
  455.  Volume of Last Trade
  456.  Total Volume for the Day
  457.  
  458.  
  459.  Figure 6:  New QuotData information that will become available through DDE.
  460.  
  461.  Bid Price
  462.  Bid Size
  463.  Ask Price
  464.  Ask Size
  465.  Year High
  466.  Year Low
  467.  Open Interest
  468.  Previous Close
  469.  Previous Bid
  470.  Dividend
  471.  Yield
  472.  Earnings
  473.  PE Price/Earnings
  474.  X Dividend
  475.  Fiscal Year End
  476.  
  477.  
  478.  Figure 7:  Microsoft Excel uses this information to communicate with
  479.             QuotData via DDE.
  480.  
  481.  Application Name    QUOTDATA
  482.  Topic Name          TICKDATA
  483.  Item Name           Security Symbol (e.g. MSFT)
  484.  
  485.  
  486.  Figure 8A:  Microsoft Excel command structure for obtaining QuotData
  487.              information via DDE.
  488.  
  489.  =INDEX(QUOTDATA|TICKDATA!Symbol,column#,row#)
  490.  
  491.  
  492.  Figure 8B: Sample Microsoft Excel DDE command.
  493.  
  494.  =INDEX(QUOTDATA|TICKDATA!AAPL,1,1)
  495.  
  496.  
  497.  Figure 10:  Charting and analysis features of QuotChart
  498.  
  499.  volume histogram
  500.  departure
  501.  ratio
  502.  spread
  503.  moving average and convergence/divergence (MACD)
  504.  fast and slow stochastics
  505.  high, low, close bar charts
  506.  
  507.  
  508.  ───────────────────────────────────────────────────────────────────────────
  509.  Utilizing the Object-Oriented Nature of Windows
  510.  ───────────────────────────────────────────────────────────────────────────
  511.  
  512.  Mark Anders and Clifford P. Ribaudo
  513.  
  514.  When we set out to create QuotChart, we had a simple objective; create a
  515.  graphical, technical analysis package that could handle real-time tick-by-
  516.  tick data. Technical analysis involves applying various mathematical
  517.  formulas, or studies, to financial price data. Some simple studies include
  518.  the calculation of moving averages or the range of a data element over a
  519.  given period of time. Often these studies either cause a smoothing of data
  520.  or amplify some rate of change, so that trends in the price can be detected.
  521.  By real time we mean that a recalc is performed each and every time a trade
  522.  occurs, in much the same way that a spreadsheet changes when you modify one
  523.  cell. QuotChart differs from many other real-time charting packages in that
  524.  it does a recalc on every price change, or tick. With most other packages,
  525.  if you are looking at a chart where each x-axis unit is ten minutes, a
  526.  recalc on some or all of the studies will only be done after that ten minute
  527.  period is finished.
  528.  
  529.  While writing Market Maker, we learned that some of the most important work
  530.  done during the creation of an application is in the planning stage, before
  531.  the actual coding begins. One of our objectives was to make the application
  532.  as modular as possible. Aside from being good programing practice, since we
  533.  hadn't chosen an operating environment at the time that we began designing
  534.  and writing QuotChart, it was important that the user interface layer be
  535.  separate from the areas responsible for performing calculations and plotting
  536.  the data on the display. In general our design decisions had to be made not
  537.  only with a view towards what we planned to release as Version 1.0, but also
  538.  attempting to anticipate where we wanted to take the product in the future.
  539.  
  540.  Because of the overriding need for flexibility, it was decided that the core
  541.  of the program would be an object-oriented language tailored to process the
  542.  incoming data stream, and that the user interface layer would generate
  543.  formulas written in this language. The object-oriented nature was necessary
  544.  so that each instance of a function call could remember the state of its
  545.  calculations for faster recalcs.
  546.  
  547.  There were disadvantages to this approach. First, there was overhead
  548.  involved because a formula had to be created, parsed, and code of some type
  549.  generated. In addition, a compiler had to be written for the language.
  550.  
  551.  The advantages, however, were numerous. Isolating the user interface from
  552.  the core code meant that as long as the syntax of the language remained the
  553.  same, you could change the actual implementation of language without having
  554.  to modify the user interface layer. The language can be viewed as a
  555.  communications mechanism between the user interface and the way the program
  556.  internally organizes its data. Because of this defined interface between the
  557.  two layers, the details of how the core code works is completely invisible
  558.  to the user interface. Since the interface has no knowledge of any internal
  559.  data structures, which of course could change, it is less likely to be
  560.  broken when modifying the core, or damage any other areas of the program
  561.  when it is modified. Portability is also increased. Since the core is only
  562.  passed strings to compile and execute, it doesn't know or care what the user
  563.  interface is like. Only those objects having to deal directly with the
  564.  outside environment, for example the object that actually draws a line,
  565.  would require modification to a run in a new environment.
  566.  
  567.  Each object in the language represents a particular operation on a data
  568.  stream. Once the piece of code is written which implements that operation,
  569.  you can reuse it in a variety of settings. For example, perhaps the most
  570.  frequently used object in the system is the object called Line. As its name
  571.  implies, it draws a line on the display (or printer). It takes three
  572.  arguments, the first being the data stream from which it receives its data.
  573.  The remaining two, which are optional, are names for the line; the first
  574.  used for display to the user, the second being a unique object name that
  575.  other objects can use to reference Line's data. The formula used to create a
  576.  line chart of the last or closing price of Microsoft (MSFT) would be:
  577.  
  578.    Line(Close("MSFT"),"Last Price of Microsoft", "l1")
  579.  
  580.  This formula computes and draws the last price for Microsoft. If the system
  581.  was to show data from that line, or allow the user to use the values
  582.  contained in that line as input for another study, it could refer to it by
  583.  the name Last Price of Microsoft.
  584.  
  585.  Another commonly used object is called SMA (for Simple Moving Average) which
  586.  computes the sum of the last n prices and divides that by n. Often a
  587.  technical analyst will overlay moving averages on a chart. If, for example,
  588.  you wanted to overlay a line of the 7-unit simple moving average of the last
  589.  price of Microsoft on the above chart you would add the following formula to
  590.  that chart:
  591.  
  592.    Line(SMA(Close("MSFT"),7), "7 unit Simple Moving Average","l2")
  593.  
  594.  This could be considered a direct application of the moving average object.
  595.  Moving averages are also used as elements of studies. For example, Moving
  596.  Average Convergence Divergence (MACD) takes the difference between two
  597.  moving averages of different lengths to create one line, called the MACD
  598.  line, and feeds this result into another moving average to create a second
  599.  line, called the signal. MACD uses a different type of moving average called
  600.  an Exponentially Weighted Moving Average (EMA). The formula for this is:
  601.  
  602.    Line (EMA(Line((EMA(Close("MSFT"),12)-EMA(Close("MSFT"),26)),"MACD","11"),
  603.          9), "Signal","l2")
  604.  
  605.  This example serves to illustrate a few points concerning the flexibility
  606.  gained by the implementation of the language. First, the objects created can
  607.  be used in a number of different contexts. For instance, notice that the
  608.  line called MACD takes its input from EMA and returns its output (the Line
  609.  object returns its input unchanged) to be used as input for another EMA.
  610.  Another benefit is not as obvious. Because EMA and SMA are both moving
  611.  averages, much of the code used to implement how each behaves is shared, so
  612.  that EMA and SMA are subclasses of the moving average class of objects. SMA
  613.  is also a subclass of the sum object, which computes the sum of the last n
  614.  data elements. There is an object called Hist which is a relative of Line
  615.  but produces histogram type output. The code used for the actual drawing is
  616.  the only code which they do not share.
  617.  
  618.  The fact that many objects share code means writing less code, and when you
  619.  need to add new functionality to a class of objects, you only have to do it
  620.  in the superclass. For instance (no pun intended!), we needed to be able to
  621.  display the value of all lines at a certain x value, which of course was
  622.  easily done by writing the corresponding code in the superclass of Line and
  623.  Hist.
  624.  
  625.  There was another persuasive reason for having the language at the core.
  626.  While a user interface that allows the user to select from options, such as
  627.  the period for a moving average, is powerful, eventually some users are
  628.  going to want to program their own studies. If this capability was not
  629.  planned for, it would require substantially more work to implement than if
  630.  it existed from the beginning. In this case, what would really be provided
  631.  is a free-form user interface to the same language and not an afterthought.
  632.  
  633.  One of the interesting things about the creation of QuotChart is that most
  634.  of the design and implementation of the language was done before Microsoft
  635.  Windows was selected as the operating environment. We were very lucky that
  636.  Windows was object-oriented and both lent itself to and encouraged modular
  637.  design. The ability to easily create menus and have tools such as the dialog
  638.  editor meant that adding new features was often just a matter of drawing a
  639.  new dialog box and providing it with the ability to generate a new formula
  640.  string. It can be tricky programming under Windows, but it has really helped
  641.  us maintain our goals of modular design without having to invent our own
  642.  object-oriented user interface.
  643.  
  644.  
  645.  Porting Apple Macintosh Applications to the Microsoft Windows Environment
  646.  
  647.  Andrew Schulman and Ray Valdes
  648.  
  649.  You're happy with your Macintosh program. It cooperates with other programs
  650.  under MultiFinder(TM), it runs like the wind in color on the Mac(R) II, and
  651.  even your customers with Mac 512Ks are fairly happy with the program. Your
  652.  development goes smoothly using the Lightspeed(R) C compiler and TMON
  653.  debugger and, though you wish Lightspeed's editor weren't so simplistic,
  654.  you've put together some dynamite EMACS macros using Quickeys(TM). You wish
  655.  you had C++, but Apple is coming out with MPW(TM) C++ soon. And you've got
  656.  to love that Mac II.
  657.  
  658.  Why then would you want to port your program to the PC, Windows, and the
  659.  OS/2 Presentation Manager (hereafter referred to as PM)?
  660.  
  661.  
  662.  A Wider World
  663.  
  664.  Windows has become a de facto standard for graphical windowed software in
  665.  the IBM world, as evidenced by its incorporation into the IBM(R) OS/2
  666.  Presentation Manager.
  667.  
  668.  Windows can be your entrée to the larger world of the Graphical User
  669.  Interface (GUI) standard. Not only is Windows on the path to OS/2
  670.  Presentation Manager, and from there to the promise of SAA, but the Windows
  671.  programming model is much closer to that of X Windows than is the Mac's.
  672.  
  673.  There's another sense in which porting to Windows will widen your
  674.  perspective. Not only is the PC software market significantly larger than
  675.  the Mac market, but there is a much wider range of hardware. Right now, Mac
  676.  software only runs on screens with square pixels. Windows is fairly device-
  677.  independent so that, if you port to Windows, your software will run even
  678.  on screens and printers with pixel ratios like 12:5, 1.43:1, and 1.37:1. You
  679.  may think such screen ratios odd, and we would agree with you, but these
  680.  ghastly monitors represent the majority of the real world.
  681.  
  682.  
  683.  Working with Windows
  684.  
  685.  If you're a Macintosh programmer who needs to learn how to program for
  686.  Windows, there is some good news and some bad news.
  687.  
  688.  The good news is that everything you know is not wrong. You already know
  689.  the central ideas behind Windows programming: event-driven input; text is
  690.  just another form of graphics output; relocatable memory; and resources.
  691.  
  692.  Ironically, a Mac programmer who has never sat down at an IBM PC is in a
  693.  better position to learn Windows than a seasoned DOS programmer who has not
  694.  yet gone through the infamous "everything you know is wrong" learning curve.
  695.  If you know Mac programming, then under Windows much of what you know is
  696.  still true.
  697.  
  698.  A Mac programmer has already learned that application code is inert and
  699.  passive, and simply waits to be activated by a data structure (called an
  700.  EventRecord on the Mac and an MSG under Windows). A C programmer on the Mac
  701.  has probably said good-bye to old friends like printf and gets and, sure
  702.  enough, you won't use these for Windows programs either. Relocatable memory
  703.  blocks? Mac programmers handle these routinely, so the fact that Windows
  704.  moves memory around is not an issue. Your Mac knowledge can be leveraged
  705.  in programming for a market with millions of IBM and IBM-compatible
  706.  computers.
  707.  
  708.  After you've worked with the application program interfaces (API) of several
  709.  graphical windowed environments, you begin to realize how similar they are.
  710.  There are many strong resemblances between the Macintosh Toolbox and the
  711.  Windows API, even some one-to-one correspondences. Take the function
  712.  PtInRect: how could determining whether a given point falls within a given
  713.  rectangle not be the same in all systems?
  714.  
  715.  
  716.  Everything Is Different
  717.  
  718.  There are many other formal analogies. For example, the Macintosh memory
  719.  manager provides a function, NewHandle, which returns a handle to a block
  720.  of relocatable memory, and the Windows kernel provides GlobalAlloc which,
  721.  when called with the GMEM_MOVEABLE flag, also returns a HANDLE to a block of
  722.  relocatable memory.
  723.  
  724.  A Windows HANDLE sounds just like a Macintosh Handle. There are many such
  725.  seeming similarities between the Mac and Windows. Believe it or not, that is
  726.  the bad news. While the Mac, Windows, and Presentation Manager are in some
  727.  formal sense identical and after a while feel all the same, it's the little
  728.  subtle differences that matter when you're porting code. Although they are
  729.  conceptually identical, when you get to the details, a Windows HANDLE is
  730.  almost nothing like a Macintosh Handle.
  731.  
  732.  Here are further illustrations of the same point: Windows regions are not at
  733.  all like regions on the Mac; the Windows function FindWindow has nothing
  734.  to do with the Mac function FindWindow; the Windows equivalent for the Mac
  735.  function FrameRect is the function Rectangle and not the function
  736.  FrameRect.
  737.  
  738.  In this article, we will first port a tiny fragment of Mac graphics code to
  739.  Windows, illustrating the similarities and differences between Mac's
  740.  QuickDraw(TM) and Windows' graphical device interface (GDI), and we will
  741.  show one approach to portable code that takes advantage of the
  742.  similarities, while isolating you from the differences. We then present a
  743.  complete application that shows a unified approach to Macintosh and Windows
  744.  events, windows, and memory management. A PC port is not only desirable, but
  745.  possible, as portable code is both desirable and possible.
  746.  
  747.  
  748.  The Update Event
  749.  
  750.  Kernighan and Ritchie in The C Programming Language, (Prentice Hall, 1988),
  751.  2nd edition, p. 5 say, "The only way to learn a new programming language is
  752.  by writing programs in it," but probably the only way to learn a new
  753.  windowed graphical environment's API is by writing parts of programs in it.
  754.  Even "Hello World" in such an environment typically involves more code
  755.  than a newcomer can be expected to type, especially on a new machine using
  756.  an unfamiliar editor.
  757.  
  758.  One of the more bizarre (until you get used to it) features of GUI
  759.  environments actually comes to our rescue in the form of redrawing old
  760.  output. Whether on the Mac or Windows, your program must be prepared to
  761.  reproduce its output at any time. The reason for this is that the window to
  762.  which you send your output (text or graphics) may get damaged.
  763.  
  764.  When this happens, the Mac sends your application an updateEvt, and Windows
  765.  sends the damaged window a WM_PAINT message. On the Mac one responds to an
  766.  updateEvt by bracketing QuickDraw calls between a BeginUpdate and an
  767.  EndUpdate and, similarly, in Windows the response to a WM_PAINT message is
  768.  to sandwich GDI calls between a BeginPaint and an EndPaint. So you can see
  769.  that programming for Windows won't be that unfamiliar.
  770.  
  771.  Since your application must be prepared to meet an update event by redrawing
  772.  the window's contents, an application can confine all of its drawing to
  773.  this one place.
  774.  
  775.  Let's begin by writing some code that will get called whenever our window
  776.  receives a WM_PAINT message. What we propose to do is take the HELLO
  777.  "application" (it's actually more of a skeleton) from the Windows Software
  778.  Development Kit (SDK), remove the call to the function HelloPaint, replace
  779.  it with some Mac code, and alter the Mac code into something that will
  780.  compile and run on the PC. We will start with as little alteration as
  781.  possible, then gradually work up to full Windows code, writing a real
  782.  version that runs under both systems.
  783.  
  784.  The Mac code found in the function StopSign illustrates QuickDraw's MoveTo,
  785.  Move, and Line functions, and is loosely borrowed from Stephen Chernicoff's
  786.  superb book Macintosh Revealed (Hayden Books, 1987), 2nd edition, Vol. I,
  787.  pp. 189-191. The code appears in Figure 1, along with bits of surrounding
  788.  context, so you can see how the function StopSign would get called on the
  789.  Mac. The function scales the stop sign to the size of the window into which
  790.  it's being drawn. If you resize the window, you not only generate a
  791.  mouseDown inGrow; you also generate an updateEvt that causes the stop sign
  792.  to be rescaled and redrawn to the new window dimensions.
  793.  
  794.  
  795.  The Hard Part
  796.  
  797.  A Mac programmer who wants to put this Mac code into a Windows program faces
  798.  one hurdle that has nothing to do with the code itself: you have to set up
  799.  the SDK and the C compiler on your PC/AT(R) or 386 machine, you need a text
  800.  editor, and you'll soon want a set of tools like grep and diff.
  801.  
  802.  Note that Windows/386 lets you perform tasks from within the graphical
  803.  windowed environment (see Figure 2), whereas Windows/286 makes you edit
  804.  and compile in character mode, just dropping into Windows long enough to
  805.  test the resulting program. Integrated programming environments such as
  806.  QuickC(R) compiler and Turbo C(R) unfortunately will not generate code for
  807.  Windows.
  808.  
  809.  If you're used to the "double-click and you're done" simplicity of
  810.  installing software on the Mac, then installing tools like C 5.1 and the
  811.  Windows SDK on your 286 or 386 is going to be more difficult. All we can
  812.  tell you is, once you can compile HELLO.EXE from the SDK, then writing code
  813.  is going to seem easy.
  814.  
  815.  
  816.  Take One
  817.  
  818.  Figure 3 shows our first take at a Windows equivalent. While we could get
  819.  the exact same Mac code to run on a PC by emulating a GrafPort and a set of
  820.  functions to manipulate it, we've already worked on such a project and the
  821.  resulting performance was unacceptably slow. Instead we're using "native"
  822.  Windows code, while trying to preserve as much of the feel of the Mac as
  823.  possible.
  824.  
  825.  The only parameter the Macintosh version of StopSign needs is a WindowPtr,
  826.  which comes along with the updateEvt in event.message. Because WindowPtr is
  827.  just another name for GrafPtr, StopSign uses this single parameter to get
  828.  both the portRect into which to scale the stop sign, and the GrafPort where
  829.  output calls are directed.
  830.  
  831.  In the Windows version, notice that we pass in two parameters: an HWND and
  832.  an HDC. An HWND is a handle to a window and an HDC is a handle to a Device
  833.  Context (DC), which is a data structure maintained by Windows that, like a
  834.  GrafPort, maintains graphics states such as the current pen, clipping
  835.  region, current position, font, and so on.
  836.  
  837.  While the Mac keeps a very close association between a window and a GrafPort
  838.  (in fact, a WindowRecord is a GrafPort, with some extra fields stuck on the
  839.  end), under Windows an HWND often has only an HDC associated with it while
  840.  the application is servicing a WM_PAINT message. One of the most important
  841.  tasks of BeginPaint is to give an HDC to an HWND that's received a WM_PAINT.
  842.  While the pairs BeginUpdate/EndUpdate and BeginPaint/EndPaint are
  843.  syntactically nearly identical, each serves a little different purpose.
  844.  
  845.  
  846.  The "Magic Cookie"
  847.  
  848.  From the code in Figures 1 and 3 we can see that, where the  Mac version
  849.  grabs the dimensions of the window's content region by directly peeking at
  850.  port->portRect, under Windows we use the API function GetClientRect. The
  851.  portRect and the client rect are conceptually similar, except the Mac
  852.  inconveniently includes scroll bars as part of the portRect and we have to
  853.  compensate for them.
  854.  
  855.  A more important difference, though, is that under Windows we made a
  856.  function call to get at something that is simply a field in a data structure
  857.  on the Mac. This is a key philosophical difference between the Mac and
  858.  Windows: Mac programs directly manipulate the fields of a GrafPort (for
  859.  example, thePort->info) while Windows programs have no knowledge of the
  860.  internal structure of a DC or of nearly any other Windows data structure,
  861.  and have to use function calls to find things out (for example,
  862.  GetInfo(hDC)).
  863.  
  864.  However, don't we have a Handle to a DC? No, we have a HANDLE. A Windows
  865.  HANDLE is not a pointer to a pointer (**hThing is the thing itself) like a
  866.  Macintosh Handle; it is a "magic cookie." In the call GetClientRect(hWnd,
  867.  &r), hWnd is the "magic cookie": we pass it in, and when the function
  868.  returns, r contains our information. We don't know where in the WND data
  869.  structure the client rect is kept, and we need not; GetClientRect is a black
  870.  box. In complete contrast, thePort->portRect is more like Pandora's box. The
  871.  Windows API is not perfect, and in some areas it is inferior to the
  872.  Macintosh Toolbox; but for data abstraction Windows wins hands down.
  873.  
  874.  In other words, the Windows API has a function-call interface (narrow
  875.  channels of communication between your program and the environment); the
  876.  Macintosh approach of working directly with the data structures and low-
  877.  memory globals approach works well right now, but the channel is too wide to
  878.  constitute an interface.
  879.  
  880.  
  881.  Being Explicit
  882.  
  883.  One of the most noticeable differences between the Mac code in Figure 1 and
  884.  the Windows code in Figure 3 is that, on the Mac a line may be drawn with
  885.  Line(8 * scale, 0), while in the Windows version we say Line(hDC, 8 * scale,
  886.  0).
  887.  
  888.  That one little hDC parameter is quite important. Whereas QuickDraw calls
  889.  operate implicitly on thePort, and therefore establish the current port
  890.  with SetPort (squirreling away a pointer to the previous port so that it can
  891.  be restored at a later time), every GDI call takes an HDC as an explicit
  892.  parameter.
  893.  
  894.  This passing around of hDC from function to function as though it were a hot
  895.  potato will seem tedious for tiny applications with only one window and
  896.  only one DC but, for real applications with multiple windows, it can
  897.  result in cleaner code than the frequent GetPort/SetPorts one often sees in
  898.  Mac code.
  899.  
  900.  
  901.  The Fleeting GrafPort
  902.  
  903.  To establish a border of 5 units on all four sides of the stop sign, both
  904.  versions reduce the size of the drawing area by 10 units and shift its
  905.  origin southeast by 5 units. On the Mac, this is done with SetOrigin. The
  906.  previous origin is saved so that, when we finish, we can restore things to
  907.  the way we found them.
  908.  
  909.  Under Windows we could use SetViewportOrg, but using OffsetViewportOrg
  910.  seemed more suitable, since then we don't need to know what the previous
  911.  origin was. Why viewport? We'll get back to that when we discuss our second
  912.  version of the stop sign.
  913.  
  914.  A more important point is that in this case it is not absolutely
  915.  necessary to undo the OffsetViewportOrg at the end of the Windows version.
  916.  Remember that an HDC is not tied tightly to a Windows window as a GrafPort
  917.  is to a Mac window. Each time BeginPaint is called, we get a fresh DC. Any
  918.  alterations we made to a DC, such as altering the origin or selecting a
  919.  different pen or font, disappear after calling EndPaint. This means that, if
  920.  you're about to call EndPaint anyway, you may be able to avoid undoing some
  921.  changes; but it also means that you have to set up what you need each time
  922.  you call BeginPaint.
  923.  
  924.  Many Mac programs will have a hard time with this fleeting GrafPort,
  925.  because they assume they can ask at any time about, say, GrafPtr->pnLoc.
  926.  When these programs are ported to Windows, the assumption is that at any
  927.  time you can call, say, GetCurrentPosition(hDC), not realizing that you
  928.  often don't have an HDC.
  929.  
  930.  There is a way for a Windows program to give each of its windows a DC that
  931.  persists even after you've called EndPaint and for which EndPaint is, in
  932.  fact, a NOP. When you establish a window class during the initialization
  933.  of a Windows program, you can specify its style as CS_OWNDC. All Windows
  934.  created with this class then have their own DC.
  935.  
  936.  Don't do this! However tempting it is for ported Mac code to use CS_OWNDC,
  937.  don't succumb to this temptation. Using CS_OWNDC results in a lot of
  938.  unnecessary window invalidation and generally causes problems in the form
  939.  of weird repainting bugs.
  940.  
  941.  
  942.  Emulating Toolbox Calls
  943.  
  944.  Returning to the code in Figure 3, note that we had to write our own Line
  945.  and Move functions. Where the Mac has LineTo, MoveTo, Line, and Move,
  946.  Windows has only LineTo and MoveTo. Here we get into one strategy for
  947.  portable code: rather than rewriting StopSign so it uses only LineTo and
  948.  MoveTo, we wrote our own Line and Move for Windows.
  949.  
  950.  For simple functions like these, emulating the Toolbox under Windows works
  951.  well. While MoveTo moves the current position to an absolute location in
  952.  the current GrafPort (on the Mac) or the designated HDC (under Windows),
  953.  Move offsets the pen relative to its current position. To write a Windows
  954.  version of Move, we need to know the current position. GetCurrentPosition
  955.  returns (x,y) packed into an unsigned long; the individual components are
  956.  broken out using the LOWORD and HIWORD macros from windows.h. Always use
  957.  these macros to extract such packed information. Never write your own code
  958.  to do so.
  959.  
  960.  While we're supplying these Mac-like routines, it is useful to make them
  961.  behave as much like other Windows functions as possible. Although we never
  962.  use the return value, we pass it along. And for consistency's sake we use
  963.  the FAR PASCAL modifiers. Both the Macintosh Toolbox and the Windows API use
  964.  the Pascal calling convention, but for different reasons. FAR is something
  965.  that we'll explain later, when we talk about memory.
  966.  
  967.  There's one other change to make: adding stop.c to the Hello make file.
  968.  While editing the make file, add the -W2 switch to the compiler command line
  969.  so that you get adequate warnings from the C compiler.
  970.  
  971.  To compile the code, all you have to type is MAKE HELLO. All sorts of
  972.  inscrutable compiler switches will fly by on the screen. If all goes well,
  973.  make will also run the linker and the RC resource compiler.
  974.  
  975.  We've been focusing on the changes to the Mac code in Figure 1 to turn it
  976.  into the Windows code in Figure 3, but the two versions are actually
  977.  similar. Both use the same formula to compute the scale by which all
  978.  coordinates are multiplied. The scale is divided by 18 because that's the
  979.  largest unit of measurement in the sequence of MoveTo/Line/Move calls.
  980.  
  981.  
  982.  A PC is Not a Mac
  983.  
  984.  It's time to run our modified HELLO.EXE. If you're developing under
  985.  Windows/386, you can just double-click on HELLO.EXE. If you're using
  986.  Windows/286, first you'll have to go into Windows. Either way, you start up
  987.  the new application and... it's awful! Unless you're running on a VGA or a
  988.  monitor with square pixels, the stop sign is horribly elongated. It's
  989.  particularly nasty on a CGA (see Figure 4). The stop sign resizes itself
  990.  nicely enough when we resize the window (so at least GetClientRect is
  991.  working for us), but the shape remains distorted.
  992.  
  993.  Here we discover that a PC, even a PC running Windows, is not a Mac. Macs
  994.  have screens with square pixels, and more and more PCs (and all PS/2(R)
  995.  machines) come with square-pixel monitors, while Windows programs have to
  996.  run on a variety of monitors, good, bad, and ugly (otherwise known as VGA,
  997.  EGA, and CGA). Windows programs can make fewer assumptions than Mac
  998.  programs.
  999.  
  1000.  For a guide to the world of PC graphics adapters, see Richard Wilton's
  1001.  Programmer's Guide to PC & PS/2 Video Systems (Microsoft Press, 1987).
  1002.  Microsoft Windows will shield you from having to know whether you're running
  1003.  in 640 x 480 or 640 x 200─if you let it.
  1004.  
  1005.  The stop sign is elongated because we're using Windows' default mapping
  1006.  mode, MM_TEXT, in which units of measurement, such as the ones we use in our
  1007.  MoveTo/Move/Line calls, represent pixels on the screen. This results in
  1008.  graphical output that depends on the display being used, but it is closest
  1009.  to the arrangement on the Mac-where all the coordinates represent pixels on
  1010.  the screen, 72 to the inch, and where there is a good match between printers
  1011.  and monitors.
  1012.  
  1013.  This presents a problem: using the default MM_TEXT mapping mode may be the
  1014.  easiest way to port Mac code to Windows, because Mac programs assume that
  1015.  a unit is a pixel and they do their own scaling. But bear in mind that
  1016.  squares will only look square on a VGA. Using the closest analogies between
  1017.  two systems does not always produce good results, so beware of using MM_TEXT
  1018.  for graphics. The units-are-pixels arrangement just happens to works well on
  1019.  monitors with square pixels by coincidence. This is why your development
  1020.  hardware should include at least one CGA monitor. Don't do your testing on
  1021.  one of those beautiful 8 1/2 x 11 paper-white monitors; everything looks
  1022.  great on those, making them nice to work with, but worthless for testing.
  1023.  
  1024.  
  1025.  Take Two
  1026.  
  1027.  Do we to have to check what monitor we're running on, and scale differently
  1028.  in the x and y directions? No, in fact, we can dispense with the (* scale)
  1029.  stuff altogether for the PC version, and let Windows do all our scaling for
  1030.  us.
  1031.  
  1032.  Windows has programmable coordinate systems. We can set up a mapping mode so
  1033.  that the stop sign will come out square, no matter what screen is used. We
  1034.  can set up our own units of measurement so that, for example, 18 units
  1035.  always fills the window no matter what the window's size.
  1036.  
  1037.  But we don't want arbitrary scaling along both the x and y axes; we want the
  1038.  stop sign to fill the window as completely as possible, without distorting
  1039.  its shape. We can do this using the MM_ISOTROPIC mapping mode, which is used
  1040.  in the analog CLOCK program that comes with Windows (and the source code for
  1041.  which comes with the Windows SDK).
  1042.  
  1043.  The Mac doesn't really have any built-in facility for programmable
  1044.  coordinate systems. Macintosh pictures have a picFrame which is scaled to a
  1045.  destination rectangle when you call DrawPicture, resulting in an arbitrary
  1046.  scaling along the x and y axis. This is similar to Windows' MM_ANISOTROPIC
  1047.  mapping mode. The Mac also has the ScalePt and MapPt functions, but there
  1048.  isn't anything as general as Windows' mapping modes.
  1049.  
  1050.  In order to use the isotropic mapping mode, Windows needs to know what unit
  1051.  of measurement you're using and what rectangle these should be mapped onto.
  1052.  For example, our stop sign seems to be in a unit of measurement whose
  1053.  largest unit is 18, and we want 18 to map to the extent of the portRect,
  1054.  minus our border of 5 units on each side-actually not that difficult.
  1055.  
  1056.  The rectangle that you're mapping into (the destRect, so to speak) is called
  1057.  the "viewport," and the unit that you're mapping from is called,
  1058.  unfortunately, the "window."
  1059.  
  1060.  We've been discussing our second version for quite a while, but it's just a
  1061.  tiny change in Figure 5. We set the map mode to MM_ISOTROPIC, and we set our
  1062.  window and viewport extents. Instead of actually deleting all the (* scale)
  1063.  code, we left it in and just set the scale to 1. If you enter the changes in
  1064.  Figure 5, recompile (MAKE HELLO), and run the new HELLO.EXE, you'll see a
  1065.  big improvement. The stop sign keeps its shape, no matter what the window
  1066.  shape, the window size, or what monitor is used.
  1067.  
  1068.  Because the mapping mode takes care of the conversion of "logical units" (a
  1069.  very powerful idea) to device units, we can also use it for the rotation of
  1070.  objects. To rotate the stop sign, just replace the line:
  1071.  
  1072.    SetWindowExt(hDC, 18, 18);
  1073.  
  1074.  with:
  1075.  
  1076.    SetWindowExt(hDC, -18, 18);
  1077.    SetWindowOrg(hDC,  18, 18);
  1078.  
  1079.  More information on using mapping modes for rotation of objects can be found
  1080.  in Brian Myers and Chris Doner's useful book Graphics Programming Under
  1081.  Windows (Sybex, 1988).
  1082.  
  1083.  
  1084.  Take Three
  1085.  
  1086.  Notice that we've been passing StopSign the window handle hWnd so it can do
  1087.  a GetClientRect each time we get an update event (WM_PAINT). Not only is
  1088.  adding this extra parameter a bit of a nuisance, but often when we get a
  1089.  WM_PAINT, the size hasn't changed.
  1090.  
  1091.  We can make a minor change to HELLO.C to keep the current client rect
  1092.  around, updating it whenever we get a WM_SIZE message. If you search through
  1093.  HELLO.C, though, you won't find any explicit handling of size messages. This
  1094.  seems like a Mac program that doesn't handle a mouseDown inGrow; how can we
  1095.  be resizing our windows as we've been doing, if the application doesn't
  1096.  handle this event?
  1097.  
  1098.  In Windows, "window classes" really do behave like classes in object-
  1099.  oriented programming: they have a default behavior. Messages not
  1100.  explicitly handled by a window procedure in its switch statement, fall
  1101.  through to the default case where they are passed to DefWindowProc. A
  1102.  Windows program doesn't manage the tracking of the mouse during window
  1103.  resizing, the way a Mac program does by calling GrowWindow and SizeWindow.
  1104.  
  1105.  Figure 6 shows the changes needed so that we can maintain the client rect
  1106.  ourselves, rather than asking Windows for it every time. We trap for the
  1107.  WM_SIZE message now, updating the global variable client_rect. StopSign no
  1108.  longer takes an HWND parameter; instead, it inspects the global variable
  1109.  client_rect. In a sense we've thinned StopSign's interface, since now it
  1110.  takes one parameter instead of two, but now we're peeking at global
  1111.  variables, which is worse. We'll fix this in our fifth version.
  1112.  
  1113.  
  1114.  Take Four
  1115.  
  1116.  If it makes sense for us to maintain our own state for the window size, it
  1117.  makes even more sense to do the same for the current position, which we've
  1118.  been inquiring after each time in our functions Move and Line. We could add
  1119.  two more global variables, curr_x and curr_y, and use them and update them
  1120.  in Move and Line-except that the current position is also changed by the
  1121.  Windows functions MoveTo and LineTo. Of course, we didn't write our own
  1122.  versions of MoveTo and LineTo, since Windows supplies these itself. In
  1123.  order to keep curr_x and curr_y up to date, we'll have to write tiny front-
  1124.  ends, MyMoveTo and MyLineTo, which simply update our variables and then
  1125.  call Windows' MoveTo and LineTo.
  1126.  
  1127.  It may sound silly, but writing this front-end is our most powerful idea
  1128.  so far. Making our own versions of the "primitives" supplied by the
  1129.  operating environment is the first real step toward portable code.
  1130.  
  1131.  If you don't have a certain routine, it's often useful to create a
  1132.  hypothetical one; textbooks on computer science call this "wishful
  1133.  thinking." Later on, you write the routine that you hypothesized you had.
  1134.  Conversely, when writing portable code it's often useful to do the
  1135.  opposite; the API provides a routine, but you pretend that it doesn't. You
  1136.  call your own interface routine instead; this routine can call the
  1137.  underlying service. When switching from one environment to another, all that
  1138.  you rewrite are your interface routines.
  1139.  
  1140.  Now that we have a little set of four output routines and two variables that
  1141.  they monitor, it makes sense to package all this in a separate module. In
  1142.  our third version we added a global variable (client_rect) that's visible
  1143.  all over the place, but here we've done things properly; curr_x and curr_y
  1144.  are statics that are visible only to the routines that use them in our new
  1145.  module, DRAW.C (see Figure 7).
  1146.  
  1147.  DRAW.H provides the external interface to the functions in DRAW.C. It also
  1148.  uses some #defines to remap all LineTo calls to MyLineTo and all MoveTo
  1149.  calls to MyMoveTo. For consistency checking, DRAW.C #includes DRAW.H. But,
  1150.  in DRAW.C we actually have to call the "real" MoveTo, not MyMoveTo. DRAW.C
  1151.  #defines the preprocessor symbol DRAW_C; if DRAW.H sees that DRAW_C is
  1152.  #defined, it doesn't change the MoveTo and LineTo calls. Writing portable
  1153.  code certainly seems to involve a lot of wrestling with the preprocessor.
  1154.  
  1155.  Don't forget to update the HELLO make file again, this time adding DRAW.C
  1156.  and DRAW.H.
  1157.  
  1158.  
  1159.  Take Five
  1160.  
  1161.  At this point, we had better get this code running back on the Mac again. By
  1162.  writing front-ends for LineTo and MoveTo, we are well on the way to
  1163.  portable code. Instead of raw in-line calls to Windows, we're calling our
  1164.  own routines, which then take care of calling Windows. Except for that hDC
  1165.  in the function parameters, the routines would just as well call the Mac
  1166.  Toolbox.
  1167.  
  1168.  The first thing we must do is get rid of that HDC parameter, yet somehow
  1169.  still have an HDC to pass to the Windows routines. We want each environment
  1170.  to do scaling in its "native" way, yet not have separate code at the
  1171.  application level.
  1172.  
  1173.  Figure 8 shows the changed portions of HELLO.C and new STOP.C. It also shows
  1174.  a fragment of a Macintosh main calling the new portable StopSign function.
  1175.  
  1176.  What is most noticeable about the new STOP.C is that it does not #include
  1177.  "windows.h" and it does not #include "QuickDraw.h". Instead we include a
  1178.  small file, called CANVAS.H, which is the external interface to a new
  1179.  module, CANVAS.C (see Figure 9), that provides one common interface to our
  1180.  two different operating environments. For instance, STOP.C calls the
  1181.  procedure offset_org, which sets the origin of a drawing context (a
  1182.  GrafPort or an HDC). If compiling for the Macintosh (#ifdef MAC), offset_org
  1183.  takes care of calling SetOrigin; otherwise it calls Windows'
  1184.  OffsetViewportOrg.
  1185.  
  1186.  But OffsetViewportOrg requires an hDC parameter and, on the Mac-depending
  1187.  on which GrafPort we want to do a SetOrigin on-we may need to switch
  1188.  thePort. How can we provide the same external interface to these two
  1189.  operations which are perhaps conceptually similar, but so completely
  1190.  different in practice?
  1191.  
  1192.  
  1193.  HCANVAS
  1194.  
  1195.  Continuing with this example, the first parameter to offset_org is hCanvas,
  1196.  a handle to a CANVAS, our new data type provided by CANVAS.C. A CANVAS
  1197.  contains all the information needed to talk to a GrafPort on the Macintosh
  1198.  and to an HWND or HDC under Windows.
  1199.  
  1200.  On the Mac, the window field of a CANVAS contains a WindowPtr; under Windows
  1201.  it contains an HWND. On the Mac, offset_org fiddles with the portRect and
  1202.  possibly switches thePort. Under Windows, we use the macro
  1203.  CANVAS_HDC(hCanvas) to extract the HDC from a CANVAS so we have an hDC to
  1204.  pass to OffsetViewportOrg. We manage to pass around some number (an
  1205.  HCANVAS) at our application level, yet we still have an HDC when the time
  1206.  comes to make the Windows call. Using the HCANVAS to get at a GrafPort or an
  1207.  HDC is handled entirely inside CANVAS.C.
  1208.  
  1209.  But how can an HDC be kept around inside a CANVAS when we know that the HDC
  1210.  is fleeting? We are not using the CS_OWNDC trick. Instead, while we are
  1211.  servicing a WM_PAINT message, the CANVAS contains a valid HDC. It is put
  1212.  there by the function begin_update. When we are done servicing an updateEvt
  1213.  or WM_PAINT, we call end_update which on the Mac calls EndUpdate and
  1214.  DrawGrowIcon, but under Windows calls EndPaint and, most important, sets
  1215.  the HDC in the CANVAS to zero.
  1216.  
  1217.  Any canvas function, like offset_org, whose Windows version requires an
  1218.  HDC, first checks to see if (!CANVAS_HDC(hCanvas)). If offset_org has been
  1219.  called between calls to begin_update and end_update, then there will be a
  1220.  valid HDC and we can make the Windows call. Otherwise, CANVAS_HDC(hCanvas)
  1221.  will be zero.
  1222.  
  1223.  In the code presented in Figure 9, we return when the HDC is zero. This is
  1224.  particularly important because a debugging version of Windows checks for
  1225.  invalid HDCs. If it finds one, it tries to send a FatalExit (RIP) code out
  1226.  to an AUX device and, if you don't have an AUX device, Windows hangs with
  1227.  the "Cannot write to device AUX" dialog box, waiting for you to attach an
  1228.  AUX device. For an introduction to debugging Windows, see Durant, Carlson,
  1229.  and Yao, Programmer's Guide to Windows (Sybex, 1988), 2nd edition, Chapter
  1230.  15.
  1231.  
  1232.  Rather than simply returning, our code should do something in order to
  1233.  acquire an HDC. This technique is used in the functions env_StartDrawing
  1234.  and env_EndDrawing in ENVRNMT.C, from which CANVAS.C has been extracted, and
  1235.  which is used in the larger application we will discuss later in this
  1236.  article. The Windows version of env_StartDrawing sets the HDC field in a
  1237.  CANVAS by calling GetDC(CANVAS_HWND(hCanvas)). GetDC is a Windows function,
  1238.  and CANVAS_HWND is our macro for extracting the HWND stored in a CANVAS. Our
  1239.  function env_EndDrawing clears the HDC field with:
  1240.  
  1241.    ReleaseDC(CANVAS_HWND(hCanvas), CANVAS_HDC(hCanvas));
  1242.    CANVAS_HDC(hCanvas) = 0;
  1243.  
  1244.  Env_StartDrawing and env_EndDrawing do something completely different on the
  1245.  Mac. At the application layer, though, the effect is the same.
  1246.  
  1247.  Looking back at STOP.C in Figure 8, note that the multiplication of (x,y)
  1248.  coordinates by a scaling factor is gone. All scaling has been moved to
  1249.  CANVAS.C. The large macro with the strange name MAYBE_SWITCHING_PORT is
  1250.  called from the Macintosh versions of move, line, move_to, and line_to, and
  1251.  multiplies the (x,y) coordinates by CANVAS_SCALE(hCanvas).
  1252.  CANVAS_SCALE(hCanvas) is set by the function set_scale on the Mac, but under
  1253.  Windows, set_scale sets the map mode, the window extent, and the viewport
  1254.  extent.
  1255.  
  1256.  As seen in Figure 9, a CANVAS contains all sorts of things besides GrafPorts
  1257.  or HWNDs and HDCs. Everything that in previous versions was scattered all
  1258.  over the code, like the global variable client_rect from the third version,
  1259.  or the statics curr_x and curr_y from the fourth, are now part of a CANVAS.
  1260.  The CANVAS has become our own machine-independent GrafPort or DC.
  1261.  
  1262.  
  1263.  Raw or Cooked
  1264.  
  1265.  While STOP.C contains no explicit Windows calls, it is still Windows code;
  1266.  it is simply portable Windows code. At the same time, while it contains no
  1267.  explicit Mac Toolbox calls, it is also Macintosh code, because the functions
  1268.  it calls in turn make Mac Toolbox calls.
  1269.  
  1270.  It is important to note that you can have Windows code that doesn't contain
  1271.  "raw" calls to OffsetViewportOrg or to BeginPaint. Something can be Mac code
  1272.  without containing raw calls to SetOrigin or BeginUpdate.
  1273.  
  1274.  Code found in good commercial applications does not resemble the code
  1275.  published in Microsoft Systems Journal or in MacTutor because, in order to
  1276.  show how to use an API, such sources must of necessity present nonportable
  1277.  system-dependent calls intermixed with higher-level code. We call this the
  1278.  raw style of coding, in contrast to the "cooked" form shown in STOP.C.
  1279.  
  1280.  Calling the cooked transform_something, rather than the raw API call
  1281.  TransformSomething, is only the beginning. Most commercial applications
  1282.  require a higher level of abstraction than that. Many commercial
  1283.  applications use typedefs and #defines to alter C so that it's unclear to
  1284.  outsiders and newcomers. However, this is essential for keeping the code
  1285.  close to the problem at hand, and for maintaining portability.
  1286.  
  1287.  Portable code is broken into an application layer (core) and an environment
  1288.  layer (edge). Ideally, the application layer doesn't change, no matter what
  1289.  system it's running under, and only #includes your own .h files. This means
  1290.  you must ensure that the application layer of your application knows nothing
  1291.  about files such as "windows.h" or "QuickDraw.h." This is a litmus test to
  1292.  determine how well you have isolated yourself from system-level
  1293.  dependencies.
  1294.  
  1295.  Depending on the level of your commitment to portability, the "only our own
  1296.  .h files" rule might even extend to the C standard library. For instance,
  1297.  while Microsoft C has the memset routine-declared in <string.h> for ANSI C
  1298.  compatibility and in <memory.h> for UNIX System V compatibility-Lightspeed C
  1299.  on the Mac has a function setmem which does the same thing, except its
  1300.  arguments are in a different order and its declaration is in <unix.h>.
  1301.  Using these routines is preferable to writing your own, because the compiler
  1302.  manufacturers are supposed to implement them efficiently (using REP STOSB on
  1303.  the PC, for example), but you also want to be shielded from minor
  1304.  differences. One solution might be a file, STD.C, to handle all nonstandard
  1305.  standard library routines, and so the environment layer might even include
  1306.  such standard routines as memset.
  1307.  
  1308.  In order to highlight the correspondence between Mac Toolbox functions and
  1309.  their Windows equivalents, our files CANVAS.C (see Figure 9) and MEMMGR.C
  1310.  (see Figure 13) are designed to compile on either environment using the flag
  1311.  #ifdef MAC. But as Rex Jaeschke points out in his new book, Portability and
  1312.  the C Language (Hayden Books, 1988), p. 9, "A general misconception is
  1313.  that exactly the same source code files must be used on all targets such
  1314.  that the files are full of conditionally compiled lines. This need not be
  1315.  the case at all." There ought to be two separate CANVAS.C files-one for the
  1316.  Mac and one for Windows.
  1317.  
  1318.  There is one serious problem with our use of #ifdef MAC. We have assumed
  1319.  that we are either compiling for the Mac or for Windows. But what about the
  1320.  OS/2 Presentation Manager, X Windows, or Display PostScript(R) systems like
  1321.  NeWS, or the NeXT computer? In actual code, you would want multiple
  1322.  versions of a file like CANVAS.C, one for each environment. Additionally,
  1323.  rather than #ifdef MAC, we might have used the various compilers' predefined
  1324.  symbols, such as THINK_C for Lightspeed C 3.0 or the Macintosh symbol
  1325.  defined by Apple's MPW C compiler.
  1326.  
  1327.  The code that we show in CANVAS.C and MEMMGR.C is filled with #ifdef MACs,
  1328.  and while it should be done with a separate CANVAS.C or MEMMGR.C for each
  1329.  environment, remember that there are no #ifdef MACs at our application
  1330.  level.
  1331.  
  1332.  While we're proposing a separate environment-specific module for each
  1333.  environment, also remember that the external interface does not change.
  1334.  There might be a CANVAS.C for the Mac, a CANVAS.C for Windows, and another
  1335.  for X Windows, but there need only be one CANVAS.H. We set up a common
  1336.  interface to disparate systems, using C files such as a Modula-2
  1337.  IMPLEMENTATION module and H files such as a DEFINITION module.
  1338.  
  1339.  There is an argument against using layers for reasons of portability:
  1340.  efficiency. But note that the same argument can be made against using C
  1341.  rather than assembler. Writing raw Windows or Mac code is in a way
  1342.  equivalent to writing in assembler.
  1343.  
  1344.  
  1345.  Pictures and Metafiles
  1346.  
  1347.  It is true that, now that we're going through the extra layer of CANVAS.C,
  1348.  our stop sign code runs a little slower. Each time we call move_to or line,
  1349.  the first parameter is checked to ensure that it's a valid HCANVAS. Before
  1350.  we can call the underlying Mac or Windows call, we must extract the
  1351.  appropriate field from the CANVAS data structure.
  1352.  
  1353.  It would be preferable to have all this validation the first time we call
  1354.  move_to or line, without continual checking every time we get an updateEvt
  1355.  or WM_PAINT message. If only there was some way to compile our CANVAS calls
  1356.  when the program starts up, and then play back the compiled object whenever
  1357.  we get an updateEvt or WM_PAINT.
  1358.  
  1359.  The Macintosh has a very general facility for doing just that: pictures.
  1360.  Between calls to OpenPicture and ClosePicture, any QuickDraw calls are
  1361.  compiled into a GrafPort's picSave field, instead of being drawn to the
  1362.  screen. The picture can be displayed by calling DrawPicture. When you're
  1363.  done with it, the picture's memory is freed by calling KillPicture.
  1364.  
  1365.  Similarly, Windows has metafiles. Between calls to CreateMetaFile and to
  1366.  CloseMetaFile, GDI calls can be sent to a metafile HDC instead of to a
  1367.  visible HDC on the screen. The metafile can be drawn by calling
  1368.  PlayMetaFile. To free the memory occupied by a metafile, call
  1369.  DeleteMetaFile.
  1370.  
  1371.  In Figure 9, the CANVAS open_picture, close_picture, draw_picture, and
  1372.  kill_picture functions provide a common interface to Mac pictures and
  1373.  Windows metafiles. In Figure 10, StopSign uses these routines, creating the
  1374.  picture once, but drawing it every time.
  1375.  
  1376.  This presents an interesting question. Since the CANVAS (GrafPort or DC)
  1377.  onto which we are drawing the picture can change in size, how does our
  1378.  scaling take place? We've saved QuickDraw or GDI calls into a picture when
  1379.  the CANVAS was one size, but when we play the picture back the CANVAS size
  1380.  may have changed.
  1381.  
  1382.  As we mentioned during the discussion of scaling in our second version, Mac
  1383.  pictures have a picFrame which is scaled to a destination rectangle when you
  1384.  call DrawPicture. This is similar to the Windows MM_ANISOTROPIC mapping
  1385.  mode, in that there is arbitrary scaling in both dimensions.
  1386.  
  1387.  In Windows, the same metafile can be drawn into any mapmode/viewport/window
  1388.  configuration. As Charles Petzold explains in Programming Windows
  1389.  (Microsoft Press, 1988), p.628, the contents of a metafile "are simply
  1390.  units. They will take on meaning only when you play the metafile." What is
  1391.  displayed when you play a metafile depends, not on the configuration when
  1392.  you saved the file, but on the configuration when you play it back. Scaling
  1393.  also works here.
  1394.  
  1395.  We have made Windows metafiles seem similar to Mac pictures. A picture can
  1396.  contain any QuickDraw calls, but only a subset of GDI calls can be compiled
  1397.  into a metafile; in particular, no Getxxxx functions can go into a
  1398.  metafile, and this was the real reason for removing the call to
  1399.  GetCurrentPosition from Line and Move in our fourth version.
  1400.  
  1401.  Since Windows is more restrictive in this situation, we'll have to restrict
  1402.  ourselves on the Mac as well if we want our code to be portable. We feel
  1403.  that accepting these restrictions is better than the other choice, as taken
  1404.  in the XVT Toolkit: that of using Windows bitmaps rather than metafiles as
  1405.  the analog for pictures.
  1406.  
  1407.  In addition to the drawing opcodes that you usually put into a picture on
  1408.  the Mac, you can also use the PicComment function to introduce arbitrary
  1409.  data into a picture. One might, for example, put PostScript commands in a
  1410.  picture comment. They are called comments because they are usually ignored
  1411.  by DrawPicture. However, by setting the commentProc field of the grafProcs
  1412.  field of a GrafPort, you can hook into the stream of PicComments and process
  1413.  them [see Scott Knaster, Macintosh Programming Secrets (Addison-Wesley,
  1414.  1988), pp. 174-181].
  1415.  
  1416.  Windows doesn't have anything exactly like this. Using EnumMetaFile to
  1417.  enumerate all the records within a metafile and PlayMetaFileRecord to play
  1418.  back an individual GDI opcode, you can define your own metafile processing.
  1419.  Windows metafiles can easily be stored on disk and shared between
  1420.  programs.
  1421.  
  1422.  Note that PostScript and the Apple(R) LaserWriter(R) are fully supported by
  1423.  Windows, but in a manner different from the LaserWriter driver's use of
  1424.  PicComments.
  1425.  
  1426.  
  1427.  Proof-by-Existence
  1428.  
  1429.  We can agree that an intermediate layer between an application and its
  1430.  environment is a useful mechanism for providing portability. The tricky part
  1431.  is the detail involved in implementing this layer. Windows and the Mac
  1432.  Toolbox are two very complex and rich environments, each with its own
  1433.  idiosyncrasies. In each environment there lurk several "gotchas" waiting to
  1434.  trip the unsuspecting programmer.
  1435.  
  1436.  Things that seem second nature to an experienced Mac programmer are
  1437.  unavailable on Windows, and vice versa. Often these discrepancies are only
  1438.  discovered when attempting to write a large application and port it from one
  1439.  environment to the other.
  1440.  
  1441.  The sample application described in this article represents our attempt to
  1442.  create a medium-sized generic application that raises as many potential
  1443.  problems as possible to provide a proof-by-existence of the validity of our
  1444.  approach. Time did not allow completion of a truly representative
  1445.  application, but the program does illustrate relevant issues regarding
  1446.  windows, events, graphics drawing, memory allocation, and command
  1447.  processing.
  1448.  
  1449.  
  1450.  Application Structure
  1451.  
  1452.  The application, GENAPP, is a first draft of a draw program that allows
  1453.  creation of graphic objects and selection and manipulation of these
  1454.  objects. Draw programs are often called object-oriented programs by way of
  1455.  contrasting them with bitmap-oriented paint programs. With paint programs
  1456.  the user manipulates the bits on the screen almost like physical entities.
  1457.  With a draw program the user manipulates abstract geometric objects such
  1458.  as rectangles or polygons through their screen representation or view.
  1459.  These geometric objects exist in some abstract mathematical space (the
  1460.  world coordinate space) separate from their screen representation.
  1461.  
  1462.  GENAPP highlights this distinction, between the set of abstract geometric
  1463.  objects and their representation on the screen display. This is achieved by
  1464.  providing both a graphics view and a textual view of the data model. For
  1465.  example, a rectangle object has both a graphic representation (the
  1466.  rectangular shape drawn on the screen) and a textual representation (a line
  1467.  of text describing the rectangle's attributes). The user can select and
  1468.  manipulate objects through either the graphics view or the text view.
  1469.  Moreover, there can be multiple graphics views and/or text views onto the
  1470.  same data model. Finally, the application can maintain multiple data models
  1471.  concurrently, in the same way word processing programs let you open
  1472.  multiple documents for editing at the same time.
  1473.  
  1474.  The program is split into these two major parts:
  1475.  
  1476.    ■  an environment-specific layer that serves as an intermediary between
  1477.       the rest of the application and the host environment, be it PC or Mac.
  1478.       This is in the file ENVRNMT.C (filenames have been truncated to eight
  1479.       characters or less because of MS-DOS requirements).
  1480.  
  1481.    ■  an environment-independent core section that represents the heart of
  1482.       the application. In a real-life substantial application, this might
  1483.       represent many modules and tens of thousands of lines of code. In our
  1484.       example, it is one file called APPLCATN.C, containing many of the
  1485.       components of larger applications.
  1486.  
  1487.  The structure of the sample application parallels in miniature the
  1488.  architecture of many illustration, word-processing, page layout, and CAD/CAM
  1489.  programs.
  1490.  
  1491.  The major elements of the environment-independent section (APPLCATN.C) are
  1492.  as follows:
  1493.  
  1494.    ■  The document manager: a document is an entity that contains a single
  1495.       data model and any number of views onto that data model.
  1496.  
  1497.    ■  The view manager: a view maintains a consistent screen representation
  1498.       of a document's data model. There can be either graphics or text
  1499.       views. A view is closely linked with a CANVAS.
  1500.  
  1501.    ■  The data model manager: a data model is owned by a document and
  1502.       contains a collection of objects. In our toy application, this is the
  1503.       only entity that is dynamically allocated via the memory allocation
  1504.       routines provided by ENVRNMT.C.
  1505.  
  1506.    ■  The object manager: implements operations on individual objects,
  1507.       such as drawing or highlighting an object or changing its attributes.
  1508.       Many of these operations are carried out by calling routines in
  1509.       ENVRNMT.C
  1510.  
  1511.    ■  The application-level event loop: requests application-level abstract
  1512.       events (APPEVENTs) from the environment-specific layer (which has its
  1513.       own internal event loop as well). Depending on the type of event
  1514.       received, calls are made to appropriate application-level modules such
  1515.       as the view manager or document manager to handle these events.
  1516.  
  1517.    ■  The menu command dispatcher: called by the application event loop
  1518.       upon receipt of a COMMAND event.
  1519.  
  1520.  The header file APPLCATN.H contains function prototypes for all the
  1521.  routines of the managers listed above. It is therefore a useful summary of
  1522.  what has been implemented and what has been left out.
  1523.  
  1524.  
  1525.  Environment-Specific Layer
  1526.  
  1527.  The file ENVRNMT.C contains environment-specific modules to deal with the
  1528.  following functions:
  1529.  
  1530.    ■  Memory allocation: functions to allocate relocatable chunks of memory,
  1531.       to resize these chunks, to lock them and get a pointer to the object's
  1532.       contents, to unlock an object, and finally to deallocate or dispose
  1533.       of an existing memory block.
  1534.  
  1535.    ■  Menu handling: environment-specific menu initialization, as well
  1536.       as handling of the system menu (known on the Mac as the Apple menu,
  1537.       which contains coresident desk accessory programs).
  1538.  
  1539.    ■  Event processing: the function GetAppEvent, which handles all
  1540.       environment-specific events and transforms them as appropriate into
  1541.       application-level events.
  1542.  
  1543.    ■  Window management: functions to create a physical window on the
  1544.       screen display (controlled by our abstract entity known as a CANVAS),
  1545.       and functions to activate, resize, update, and move these physical
  1546.       windows. The internal CANVAS data structures are maintained in
  1547.       consistency with the physical windows in the environment.
  1548.  
  1549.    ■  Graphics rendering: these functions are closely linked with the window
  1550.       management functions above by means of the CANVAS entity. The
  1551.       functions draw basic graphic shapes on the screen (rectangle, line,
  1552.       ellipse, and text), and maintain a coordinate system which is
  1553.       independent of the screen aspect ratio.
  1554.  
  1555.    ■  Environment initialization: on the Mac, initialize the appropriate
  1556.       managers. Under Windows, register the application window classes.
  1557.  
  1558.  The key factor is that the external interface to this environment-
  1559.  specific code is, like our earlier CANVAS code, environment-independent.
  1560.  
  1561.  
  1562.  What's Missing
  1563.  
  1564.  Due to publication deadlines, large areas of functionality provided by the
  1565.  Mac and Windows are completely ignored by GENAPP and its environment layer.
  1566.  These include all functions having to do with: printing, reading and
  1567.  writing of files, putting up dialog boxes, dealing with scroll bars and
  1568.  scroll events, and tracking the mouse to interactively resize an object.
  1569.  
  1570.  With a few exceptions, it is our opinion that these functional areas are
  1571.  conceptually similar enough that adding them to the existing
  1572.  environment/application framework can be a largely straightforward (albeit
  1573.  lengthy) exercise for the reader. Still, there are probably enough gotchas
  1574.  for the exercise to remain interesting.
  1575.  
  1576.  One messy area is the tracking of the mouse, for interactively resizing or
  1577.  rubberbanding a graphic object. An approach that should prove viable here is
  1578.  the one used by the Mac TEClick, TrackGoAway, or DrawWindow routines. In all
  1579.  these cases, the mouse click is used to initiate an internal mouse tracking
  1580.  process that continues until the mouse button is released. The actual
  1581.  rendering of the graphic object can then be implemented by an application-
  1582.  supplied callback routine.
  1583.  
  1584.  GENAPP provides a skeleton for adding this kind of functionality. Also
  1585.  missing from the sample application is a lot of detail on existing sections
  1586.  in ENVRNMT.C. For example, the memory management section has no function
  1587.  that returns the size of a memory block. Adding one would be trivial for
  1588.  both the Mac and Windows (through calls to Mac GetHandleSize or Windows
  1589.  GlobalSize). It happens that our sample application does not need this
  1590.  particular function.
  1591.  
  1592.  Our section on graphics rendering only supplies a few basic primitives
  1593.  (for rectangle, ellipse, and text). Both the Mac and Windows have many more
  1594.  drawing primitives that can be mapped to each other. One known area of
  1595.  difficulty is the treatment of fonts and text attributes. Windows has a
  1596.  complex largely undocumented font selection method which is both very
  1597.  different from that on the Mac, and substantially changed for Presentation
  1598.  Manager.
  1599.  
  1600.  
  1601.  Getting Input
  1602.  
  1603.  So far, we have examined how an application draws correct output to the
  1604.  screen display. This is done in a portable fashion by introducing an
  1605.  intermediate layer between the application and the GUI. An equally
  1606.  important subject is how the application program gets its input from the
  1607.  user.
  1608.  
  1609.  Programmers writing nongraphics applications formerly had to deal with
  1610.  characters entered at the keyboard, or perhaps coming in from a serial
  1611.  port. As Mac programmers know, the Mac environment dramatically extended the
  1612.  range of possible inputs, to include not just keystrokes, but also mouse
  1613.  actions (mouse up, mouse down) and other possible inputs (network, device
  1614.  driver, and internally derived inputs). Because of the high degree of
  1615.  interactivity between program and user, it became useful to conceive of an
  1616.  application's structure as passive and event-driven, rather than as active
  1617.  and data-oriented (as found, for example, in old-fashioned batch utilities
  1618.  that transform or filter simple streams of data).
  1619.  
  1620.  Unlike batch-oriented programmers, Mac programmers are familiar with the
  1621.  concept of structuring an application as a passive event loop that waits for
  1622.  and responds to a variety of events. These events include inputs from the
  1623.  user as well as internally derived inputs such as window activation and
  1624.  window updating events.
  1625.  
  1626.  To encompass all the information an application would need about an event,
  1627.  the Macintosh defines the EventRecord data structure, and Windows defines
  1628.  the similar MSG data structure. Both structures include what type of event
  1629.  occurred (event.what on the Mac, msg.message under Windows), the time it
  1630.  happened (event.when or msg.time), as well as where the mouse was
  1631.  (event.where or msg.pt).
  1632.  
  1633.  The Mac and Windows are very similar here, but the terminology may be a
  1634.  little confusing. While Mac events are called messages in Windows, note that
  1635.  the Mac EventRecord also refers to a message. This is the extra information
  1636.  that comes along with an event; its meaning differs depending on event.what.
  1637.  For instance, if (event.what == keyDown), then event.message will contain
  1638.  the ASCII code and key code. Under Windows, this same type of information is
  1639.  kept in the fields wParam and lParam (see Figure 11).
  1640.  
  1641.  
  1642.  Many Messages
  1643.  
  1644.  Most of the changes from Mac events to Windows messages involve making
  1645.  familiar concepts more flexible and more general.
  1646.  
  1647.  Inside Macintosh defines eleven types of system events. By way of contrast,
  1648.  the standard Windows header file defines 96 types of messages. What do these
  1649.  96 messages consist of?
  1650.  
  1651.    ■  Twenty types of mouse messages (rather than two mouse event types on
  1652.       the Mac). These types are necessary to handle one, two, and three
  1653.       button mice. They also treat a double-click as its own type, rather
  1654.       than relying on the application program to detect it (assuming that the
  1655.       window has been registered as one accepting double-clicks). In
  1656.       addition, there is a parallel set of mouse messages dealing with
  1657.       events in the non-client area of a window (known to Mac programmers
  1658.       as what lies outside the inContent part of the window: title bar,
  1659.       close box, etc). Unlike the Mac, where programs have to query the
  1660.       environment with GetMouse calls during null events, Windows considers
  1661.       simple movement of the mouse to be sufficiently interesting to merit
  1662.       its own WM_MOUSEMOVE message. Note that the WM prefix stands for
  1663.       Window Message, as every MSG includes the window for which it is
  1664.       intended.
  1665.  
  1666.    ■  Twelve types of keystroke messages (rather than three on the Mac). Some
  1667.       of these deal with system keys, which are distinguished from other
  1668.       keys.
  1669.  
  1670.    ■  Fifteen types of clipboard messages (rather than none on the Mac).
  1671.       These messages inform the application of changes in the content of the
  1672.       clipboard, in the chain of clipboard viewers, or in various other
  1673.       clipboard-related requests. See "Exchanging Data Between Applications
  1674.       Using the Windows Clipboard," MSJ, (Vol. 3, No. 5).
  1675.  
  1676.    ■  Twenty-three types of window messages (rather than two on the Mac).
  1677.       Some of this population explosion results from parallel sets of
  1678.       messages dealing with events in the content area of a window and
  1679.       outside the content area. Additional message types come from the fact
  1680.       that the Windows environment does the equivalent of a Mac FindWindow
  1681.       call for you in response to a mouse-down event. This means that a user
  1682.       action such as clicking on the close box of a window is abstracted into
  1683.       a WM_CLOSE message, rather than being passed to the application as a
  1684.       mouse-down event requiring further interpretation and decoding.
  1685.       Similarly, Windows does not require the application program to worry
  1686.       about mouse-down events in the menu bar. These are tracked by the
  1687.       environment and abstracted into appropriate menu COMMAND messages.
  1688.  
  1689.  Even though Windows has a WM_NULL message, this is not nearly as important
  1690.  to Windows programs as a nullEvent is to Mac programs. Many Mac programs
  1691.  use nullEvent to handle a multiplicity of housekeeping tasks, such as menu
  1692.  coloring. This has been a problem for Mac programmers wanting to modify
  1693.  their programs to be MultiFinder-friendly. Apple now strongly discourages
  1694.  programmers from doing a lot of processing when handling a nullEvent.
  1695.  
  1696.  This brings up a major difference between Windows and the Mac: Windows was
  1697.  designed from the start to be multitasking. In practice, this is a difficult
  1698.  job because Windows is not an operating system, but an application-level
  1699.  piece of software running on top of MS-DOS(R). This affects its performance,
  1700.  not its design. Because Windows was designed to have multiple applications
  1701.  executing concurrently, it is not surprising that there is much greater
  1702.  support for interapplication communication and data transfer. This is
  1703.  done through flexible user-defined messages (much better than app1Evt on the
  1704.  Mac), clipboard messages, and message protocols such as Dynamic Data
  1705.  Exchange (DDE).
  1706.  
  1707.  An analogous situation exists on the Mac with regard to networking. The
  1708.  Mac was designed to support a cheap built-in local area network (the
  1709.  AppleTalk(R) port has always been standard on Macs, so it is not surprising
  1710.  that the Toolbox has a network event type). In contrast, most PC-compatible
  1711.  machines that Windows runs on presently lack hardware to support local area
  1712.  networks, so this message type is missing from Windows.
  1713.  
  1714.  
  1715.  Events and Messages
  1716.  
  1717.  Fortunately for the programmer, though Windows gives you 96 types of
  1718.  messages, it also provides a default handler for them, called DefWindowProc.
  1719.  This brings up a significant difference between event/message handlers on
  1720.  the Mac and on Windows.
  1721.  
  1722.  On the Mac, applications wait in a loop, requesting events with
  1723.  GetNextEvent, or with WaitNextEvent, which is used by MultiFinder-aware
  1724.  applications and specifying which events they are interested in handling
  1725.  by means of an eventMask. The following code should be very familiar to Mac
  1726.  programmers:
  1727.  
  1728.    while (! done)
  1729.    {
  1730.    if(GetNextEvent(
  1731.        everyEvent, &theEvent))
  1732.    {
  1733.      switch (theEvent.what)
  1734.        {
  1735.         case mouseDown:
  1736.           doMouseDown;
  1737.           break;
  1738.         case updateEvt:
  1739.           doWindowUpdate;
  1740.           break;
  1741.             ∙
  1742.             ∙
  1743.             ∙
  1744.         }
  1745.     }
  1746.    }
  1747.  
  1748.  In Windows, all programs need a callback function for each type of window
  1749.  they create. This callback function is known as a window procedure, or
  1750.  WndProc. You must call Windows first, to get it to call you, with a message
  1751.  loop that looks similar to the Macintosh event loop:
  1752.  
  1753.    while (GetMessage((LPMSG) &msg, NULL, 0, 0))
  1754.    {
  1755.      TranslateMessage((LPMSG) &msg);
  1756.      DispatchMessage((LPMSG) &msg);
  1757.    }
  1758.  
  1759.  DispatchMessage is the Windows routine that decides which of the various
  1760.  message handling WndProcs it will call. In some ways it is similar to
  1761.  FindWindow on the Mac, transforming a low-level raw event, like a mouse-
  1762.  down, into a slightly higher-level, more abstract event, like a menu-bar
  1763.  click.
  1764.  
  1765.  Windows has a routine, WindowFromPoint, that does the same thing as
  1766.  FindWindow, but it is hardly ever used because of the all-important
  1767.  DispatchMessage.
  1768.  
  1769.  By the way, note that the return value of the Windows GetMessage is a
  1770.  Boolean value telling the application when to quit. In contrast,
  1771.  GetNextEvent returns a Boolean value telling the application whether or not
  1772.  it should handle the particular event that just passed.
  1773.  
  1774.  The message-handling WndProc on Windows (the one called by DispatchMessage)
  1775.  typically looks like this:
  1776.  
  1777.    long far pascal myWndProc( hWnd, message, wParam, lParam) HWND hWnd;
  1778.     int message;
  1779.     WORD wParam;
  1780.     LONG lParam
  1781.     {
  1782.       switch (message)
  1783.       {
  1784.         case WM_SYSCOMMAND:
  1785.           doSysCommand( hWnd, wParam,lParam);
  1786.           break;
  1787.         case WM_CREATE:
  1788.           doCreate(hWnd, wParam, lParam);
  1789.           break;
  1790.             ∙
  1791.             ∙
  1792.             ∙
  1793.       }
  1794.     }
  1795.  
  1796.  As you can see, the parameters to a WndProc callback are nothing more than
  1797.  the key components of the Windows MSG structure shown in Figure 11.
  1798.  
  1799.  Parenthetically, note that Windows uses a Pascal calling convention similar
  1800.  to that on the Mac, though for different reasons. The Microsoft C compiler
  1801.  requires this non-K&R keyword to be in a different place from where the
  1802.  Macintosh C compiler looks. Note also the additional non-K&R keyword, far,
  1803.  which refers to how this function is addressed in memory.
  1804.  
  1805.  
  1806.  Object-Oriented Windows
  1807.  
  1808.  The significant difference between Mac and Windows event handling is
  1809.  that-as opposed to a single event stream on the Mac that contains both
  1810.  application-specific events as well as system-level events (desktop events
  1811.  such as menu-bar click) for all windows-Windows uses multiple message
  1812.  streams flowing to each type of application window. Windows applications,
  1813.  therefore, have as many message handlers as there are window types.
  1814.  
  1815.  Why did Microsoft make this change? It comes from an approach that extends
  1816.  the Macintosh Toolbox's modular structure toward object-oriented
  1817.  programming. On the Macintosh the various subsystems are controlled by
  1818.  managers, which generally own one particular data structure (such as an
  1819.  EventRecord or WindowRecord). Application programmers call Toolbox routines
  1820.  to manipulate these data structures. (Ignore for now the fact that Apple
  1821.  makes these data structures available for direct manipulation by the
  1822.  programmer.)
  1823.  
  1824.  In Windows, a window is not just a data structure manipulated by
  1825.  WindowManager routines. It is conceived as an objectlike entity of a
  1826.  particular type, or class, and therefore exhibits behavior associated with
  1827.  that class. A window's behavior is defined as its particular responses to
  1828.  messages it may receive. The generic or archetypal window has generic
  1829.  behavior that is defined by the default message handler. This default
  1830.  message handler (DefWindowProc) is provided by the Windows environment.
  1831.  
  1832.  Conceptually the desktop can be thought of as the root or parent window to
  1833.  multiple applications. Applications each have their own main window (with
  1834.  its associated menu bar of commands), and may have any number of subsidiary,
  1835.  or child windows. These windows may all be the same type (representing a
  1836.  document or page), or they may be various types (document window, palette
  1837.  window, status window, etc.). Each of these types can therefore behave
  1838.  differently. Similarly the system has different classes of windows for
  1839.  different purposes: dialog boxes, alert boxes, controls, menus.
  1840.  
  1841.  When an application starts up, it registers its classes of windows with the
  1842.  system. It must provide a message handling procedure for each registered
  1843.  class. This message handler is part of a chain of message handlers, and is
  1844.  passed all messages that flow through from the system to that particular
  1845.  window type. The handler will examine the message stream for message types
  1846.  it is interested in, and pass all other messages to the next handler on this
  1847.  chain, which may simply be the default window class handled by
  1848.  DefWindowProc.
  1849.  
  1850.  When we said earlier that clicking on the close box of a window is
  1851.  abstracted into a WM_CLOSE message-rather than being passed to the
  1852.  application as a raw mouse-down event-this wasn't quite true. The
  1853.  application does see the low-level event (it's called WM_NCHITTEST, for non-
  1854.  client hit test), but most applications just pass it on to DefWindowProc,
  1855.  where it's gradually transformed into higher-level events such as WM_CLOSE.
  1856.  
  1857.  Programmers writing applications specifically for Windows have learned to
  1858.  use the technique called sub-classing, which modifies the behavior of some
  1859.  other existing window class (system or application) by chaining a message
  1860.  handler in front of the handler for an existing window class. This new
  1861.  handler needs to implement the behavior being added or changed, and can
  1862.  then pass other messages to the existing handler to implement the standard
  1863.  behavior.
  1864.  
  1865.  This is an elegant, general, and flexible scheme that unfortunately does
  1866.  not map very well to the more limited system on the Macintosh. In the Mac's
  1867.  defense, it should be noted that there are certain window features on the
  1868.  Mac which are not provided by Windows. For example:
  1869.  
  1870.    ■  On the Mac, you can specify at window-creation time the particular
  1871.       layering order (that is, front-to-back) for the window you are
  1872.       creating.
  1873.  
  1874.    ■  You can also, if you wish, specify a callback procedure to radically
  1875.       customize a window's behavior. For example, you can specify a window-
  1876.       frame routine that might draw elliptically shaped windows rather than
  1877.       rectangular ones. This type of function is called a windowDefProc on
  1878.       the Mac, which sounds like but has nothing to do with Windows'
  1879.       DefWindowProc.
  1880.  
  1881.  Writing portable applications that run on more than one environment
  1882.  requires either a subset of features common to both environments, or
  1883.  simulating the missing functionality on the lesser environment. In the case
  1884.  of window types, we have chosen the former approach in our sample
  1885.  application. The window types used by our application are very simple, and
  1886.  map easily to both Mac and Windows. In the case of events/messages, we have
  1887.  chosen an approach that simulates a small amount of functionality that is
  1888.  missing on the Macintosh but present under Windows.
  1889.  
  1890.  
  1891.  Application Events
  1892.  
  1893.  In our sample application, there is an intermediate layer that shields the
  1894.  core part of the program from the differences between Macintosh events and
  1895.  Windows messages. This is done by defining an entity known as an application
  1896.  event or APPEVENT.
  1897.  
  1898.  The structure of our application is similar to a Macintosh program, in
  1899.  that there is a single top-level event handler that loops through and
  1900.  processes events as they occur. The event handler gets these APPEVENTs from
  1901.  the environment through calls to GetAppEvent.
  1902.  
  1903.  As an example, the application-level event loop in APPLCATN.C looks
  1904.  something like this:
  1905.  
  1906.    while (! done)
  1907.    {
  1908.      env_GetAppEvent(&theAppEvent);
  1909.      switch (theAppEvent.event_type)
  1910.      {
  1911.    case CMD_EVENT:
  1912.      doCommand( &theAppEvent);
  1913.      break;
  1914.    case MOUSEDOWN_EVENT:
  1915.      doMouseDown( &theAppEvent);
  1916.      break;
  1917.        ∙
  1918.        ∙
  1919.        ∙
  1920.      }
  1921.    }
  1922.  
  1923.  The above example is very similar to the Mac event loop shown earlier. The
  1924.  differences are in the data structure used (of type APPEVENT rather than
  1925.  type EventRecord), and the kinds of events processed. There are 12 different
  1926.  types of APPEVENTs, representing what's needed to achieve our purpose.
  1927.  
  1928.  If our sample application were to evolve into greater functionality and
  1929.  complexity, it is likely that additional types of events would be needed.
  1930.  But this would be some number less than 20, rather than the 96 types defined
  1931.  for Windows. The 12 APPEVENT types are shown in Figure 12.
  1932.  
  1933.  The alert reader will note that there are six types of view events, and
  1934.  views, while not yet discussed, are presumably analogous to windows. But
  1935.  the Macintosh has only two event types dealing with windows (update and
  1936.  activate). Where do these other types come from?
  1937.  
  1938.  Our intermediate environment-specific layer contains an internal event
  1939.  loop that actually handles the "real" events provided by the Macintosh or
  1940.  Windows environment. The particular environment-specific events are
  1941.  transformed by this internal handler into the more abstract APPEVENTs seen
  1942.  by the application. Windows, with its 96 message types, has to screen and
  1943.  dispose of uninteresting messages internally. The Macintosh has to handle
  1944.  more primitive actions (such as a mouse-down in the menu bar), and translate
  1945.  these into menu CMD_EVENTS similar to the WM_COMMAND messages found in
  1946.  Windows.
  1947.  
  1948.  
  1949.  Memory Management
  1950.  
  1951.  Memory allocation is one area where the Mac and Windows approaches seem
  1952.  extremely similar. In fact, there appears to be almost a one-to-one
  1953.  correlation between Mac and Windows memory functions. This close
  1954.  correlation masks some very significant differences that can cause major
  1955.  difficulties for experienced Mac programmers.
  1956.  
  1957.  However, once a Mac programmer accepts some rigid but useful constraints,
  1958.  and becomes aware of differences in the underlying hardware memory
  1959.  architecture, writing portable code can be straightforward──as the sample
  1960.  code in APPLCATN.C shows, calling environment-independent memory
  1961.  allocation primitives in ENVRNMT.C.
  1962.  
  1963.  The memory allocation code in ENVRNMT.C is basically a series of one-liners,
  1964.  with one line calling a Mac function and another the corresponding Windows
  1965.  routine. For instance, env_AllocMem allocates a relocatable memory block by
  1966.  calling Mac NewHandle or Windows GlobalAlloc, and env_GraspMem locks a
  1967.  memory block by calling Mac HLock or Windows GlobalLock. Our environment-
  1968.  independent memory functions are shown in Figure 13.
  1969.  
  1970.  These environment-independent one-liners contribute to the deceptive
  1971.  appearance of similarity. On both systems, you request a relocatable
  1972.  chunk of memory and receive what both systems call a handle to an object.
  1973.  But, as we have already said, a Mac Handle is not a Windows HANDLE. A Mac
  1974.  Handle is a 32-bit pointer to a pointer to the actual object in memory. A
  1975.  HANDLE in Windows, however, is an abstract 16-bit integer used internally
  1976.  by the environment to keep track of memory objects.
  1977.  
  1978.  
  1979.  Magic Cookie or **?
  1980.  
  1981.  Macintosh programmers have come to rely on the double-indirection property
  1982.  of handles in order to reference an object's content. That is, if ppo is a
  1983.  Macintosh handle (that is, a pointer to a pointer to an object), then the C
  1984.  language construct *ppo gives you the pointer to the object (po), and **ppo
  1985.  gives you the object's contents (o). For example:
  1986.  
  1987.    typedef struct {
  1988.        int first_field;
  1989.        int second_field;
  1990.      } SOME_OBJECT;
  1991.    Handle aHandle = NewHandle(sizeof(SOME_OBJECT));
  1992.    (**aHandle).first_field = some_value;
  1993.    (**aHandle).second_field = another_value;
  1994.  
  1995.  In contrast a Windows programmer obtains a pointer to an object by means
  1996.  of a Lock request to the system. This always happens before referencing
  1997.  the object's content, and is then followed by a corresponding Unlock
  1998.  request after use:
  1999.  
  2000.    HANDLE aHandle = GlobalAlloc(GMEM_MOVEABLE, sizeof(SOME_OBJECT));
  2001.      SOME_OBJECT far *ptr = (SOME_OBJECT far *)
  2002.      GlobalLock(aHandle);
  2003.      ptr->first_field = some_value;
  2004.      ptr->second_field = another_value;
  2005.      GlobalUnlock(aHandle);
  2006.  
  2007.  Although the Mac has similar functions to lock and unlock a memory object,
  2008.  many Mac programmers will only lock an object when repeated references are
  2009.  going to be made. For the quick-and-easy assignment of a value into a single
  2010.  field of an object, it is much more convenient to use the double-
  2011.  indirection method illustrated above. Referencing of a memory object's
  2012.  content via the "star-star" approach is an atomic or indivisible operation
  2013.  (at the C language statement level, though in C++ one could overload the *
  2014.  dereferencing operator), and therefore does not require locking an object,
  2015.  because the Mac Toolbox has no opportunity to shuffle memory around within
  2016.  that C language construct.
  2017.  
  2018.  Even when using the Mac lock/unlock functions, many Mac programmers have
  2019.  learned the exact circumstances under which the Mac Toolbox will shuffle
  2020.  memory, and therefore often use a pointer to a memory object without locking
  2021.  the object. This won't work in Windows. The Mac programmer has two choices
  2022.  for handling this discrepancy between the two environments. One is to
  2023.  simulate the feature missing from the environment by means of an
  2024.  intermediate layer of software at the application level. The other is to
  2025.  restrict oneself to the functionality that is common to both
  2026.  environments.
  2027.  
  2028.  In the case of Mac events and Windows messages discussed previously, it was
  2029.  relatively easy to simulate the Windows message types that were "missing"
  2030.  on the Macintosh: COMMAND_EVENT, QUIT_EVENT, etc. In the case of memory
  2031.  management, however, this kind of simulation is much more difficult.
  2032.  
  2033.  We once wrote a large piece of code to simulate Macintosh-style ** handles
  2034.  on Windows, but there was a significant loss in performance. Our current
  2035.  opinion is that the Mac programmer is better off learning a little
  2036.  discipline and limiting himself to the more restricted style of Windows
  2037.  memory management.
  2038.  
  2039.  
  2040.  Additional Discrepancies
  2041.  
  2042.  It is helpful to note that the lock/unlock functions do not nest on the
  2043.  Macintosh but do on Windows. Every Lock statement needs to be balanced by
  2044.  a corresponding Unlock in Windows. On the Mac, a single Unlock on a
  2045.  particular object will take effect immediately, regardless of how many Lock
  2046.  requests have previously occurred. The Mac uses a single bit per object to
  2047.  maintain lock information, while Windows uses a 16-bit counter incremented
  2048.  and decremented by respective Lock and Unlock requests. In GENAPP the
  2049.  lock operations do not nest at all, so this will work on either system.
  2050.  
  2051.  Another difference is that Windows' GlobalReAlloc, unlike the Mac's
  2052.  conceptually identical SetHandleSize, can return a different handle than the
  2053.  one given at object creation time. This means that applications should
  2054.  have a single place where a handle's value is stored; otherwise copies of
  2055.  that handle that are left lying around in other data structures may become
  2056.  invalid when that block is resized by some other part of the program.
  2057.  
  2058.  A further difference between corresponding functions is that Windows
  2059.  allocates memory in units of 16 bytes. Say you ask for a 1-byte object in
  2060.  Windows, then later request its size with a call to GlobalSize (which is
  2061.  equivalent to GetHandleSize on the Mac). The size returned by GlobalSize
  2062.  will be 16 bytes, while on the Mac it will be 1. For certain application
  2063.  programs that use this value to control further processing, it would be an
  2064.  annoying, but resolvable, difference.
  2065.  
  2066.  
  2067.  Environment-Independent Memory Functions
  2068.  
  2069.  As stated earlier, you can sidestep the mismatch between Windows and Mac
  2070.  memory functions by using an environment-independent layer of functions
  2071.  common to both environments. This approach will impose some constraints on
  2072.  the Mac programmer.
  2073.  
  2074.  Consider the following function from the sample application in APPLCATN.C.
  2075.  AddGraphicsObjectToModel adds a graphics object to the previously allocated
  2076.  data model. Writing the code as a Mac programmer would, with ** memory
  2077.  references and no locking, we get the code shown in Figure 14.
  2078.  
  2079.  In AddGraphicsObjectToModel, the argument data_model is of type HMODEL, and
  2080.  is a handle to a collection of graphic objects. Don't confuse graphic
  2081.  objects (which are geometric entities like a rectangle object or an Ellipse
  2082.  object), with memory objects. The data_model is a memory object which was
  2083.  previously allocated with a call to AllocMem.
  2084.  
  2085.  The data_model, as a dynamic array of graphic objects, must grow in size in
  2086.  order to contain the new graphic object being added to the model. This
  2087.  occurs with a call to ReAllocMem, which on the Mac calls SetHandleSize and
  2088.  in Windows calls GlobalReAlloc. Once the data_model has grown to a larger
  2089.  size, our sample function calls a subordinate routine named
  2090.  MakeGraphicsObject to set the fields in the graphic object with attribute
  2091.  information (such as rectangle dimensions or whether it is round-
  2092.  cornered).
  2093.  
  2094.  The code in Figure 14 would need several modifications to run under
  2095.  Windows:
  2096.  
  2097.    ■  The data_model argument needs to be a pointer to a handle rather than a
  2098.       handle, because its value may change when the memory block is resized
  2099.       (though in this particular piece of code it won't).
  2100.  
  2101.    ■  The data_model needs to be locked and unlocked twice-first to get the
  2102.       value of the num_objects field (and increment this field), and
  2103.       second to call MakeGraphicsObject with a pointer to the new graphics
  2104.       object. Between these two steps, the data_model needs to be unlocked so
  2105.       that it can be resized.
  2106.  
  2107.    ■  The pointer to the data_model needs to be of type PMODEL rather than
  2108.       MODEL * due to the segmented memory architecture of the Intel CPU.
  2109.       Likewise, the argument to MakeGraphicsObject needs to be of type
  2110.       POBJECT rather than OBJECT *.
  2111.  
  2112.  The revised portable version of AddGraphicsObjectToModel is shown in
  2113.  Figure 15.
  2114.  
  2115.  
  2116.  Still More Differences
  2117.  
  2118.  There are some big differences between Windows and the Mac in their
  2119.  underlying operating system and CPU architecture. These differences are
  2120.  not the sort that manifest themselves in formal discrepancies between
  2121.  memory functions in one environment or the other (such as those we have been
  2122.  discussing until now). The manifestation is more subtle, yet differences
  2123.  are more significant.
  2124.  
  2125.  These hardware and operating system differences, in order of importance, are
  2126.  as follows:
  2127.  
  2128.    ■  the 640Kb memory limitation imposed by MS-DOS
  2129.  
  2130.    ■  the size difference between near and far pointers caused by the
  2131.       segmented architecture of the Intel CPU
  2132.  
  2133.    ■  the differences in byte ordering between the Motorola(R) and Intel(R)
  2134.       CPUs.
  2135.  
  2136.  In practice, these differences cause more problems for Mac programmers than
  2137.  the imprecise correlation between memory management functions. The
  2138.  following sections describe these differences in more detail, presenting
  2139.  strategies to deal with the consequent problems.
  2140.  
  2141.  
  2142.  The NUXI Problem
  2143.  
  2144.  Hacker folklore says that it was UNIX(R) programmers who first faced the
  2145.  differences in machine byte ordering between different brands of CPUs. These
  2146.  programmers were porting UNIX software from one hardware platform to
  2147.  another and found that, if you stored the word UNIX as a sequence of bytes
  2148.  on one machine (for example, the PDP-11 minicomputer), then moved this
  2149.  data to another machine that had a different memory architecture (the Sun
  2150.  workstation, for instance), the stored text would be printed out as "NUXI"
  2151.  (depending on how the memory access was made).
  2152.  
  2153.  Other literature calls this the difference between "big endian" and "little
  2154.  endian" machines. Given a 16-bit number (that is, two bytes worth), one CPU
  2155.  will store the high-order value of that number in the byte with the bigger
  2156.  memory address, while another CPU will do the opposite. A similar situation
  2157.  exists with 32-bit long integers in the storage and accessing of the high-
  2158.  order and low-order words of that numerical quantity.
  2159.  
  2160.  GENAPP does not encounter this problem because, in its current form, it
  2161.  does not support reading and writing of data models to disk, and therefore
  2162.  data cannot be transferred from one machine to the other. In real-world
  2163.  applications, we have found the following rules useful for staying out of
  2164.  NUXI trouble:
  2165.  
  2166.    ■  When problems occur, it is because a value was written by one method
  2167.       (for example, bytes), then read back on a different machine by a
  2168.       different method (for example, words). Avoid this.
  2169.  
  2170.    ■  Reading and writing an object a byte at a time will give the same
  2171.       results on both machines.
  2172.  
  2173.    ■  Reading an object's value by using the same C language construct that
  2174.       wrote that object's value will not cause problems (or, at least won't
  2175.       cause problems that can't be remedied by machine-specific header file
  2176.       definitions).
  2177.  
  2178.  
  2179.  So Near, Yet So Far
  2180.  
  2181.  The segmented architecture of the Intel CPU used by Windows machines causes
  2182.  different sized pointers. These two sizes of memory pointers are called near
  2183.  and far, and place the elusive goal of portability a little farther away
  2184.  from the Mac programmer.
  2185.  
  2186.  Many Mac programmers have learned to write software only on the Macintosh
  2187.  and often produce reckless code like the following call to the Mac
  2188.  Toolbox NewWindow function:
  2189.  
  2190.    WindowPtr w = NewWindow( 0, &rect, "\pUntitled", 1,0,-1,0,0);
  2191.  
  2192.  The least of the sins committed by the above code is the lack of naming
  2193.  conventions to give a hint as to the types of arguments to the NewWindow
  2194.  function. It would be nice if C let you use labels inside a function call,
  2195.  so that you could use named parameters:
  2196.  
  2197.    WindowPtr w = NewWindow(wStorage: 0, boundsRect: &rect, title:
  2198.              "\pUntitled", visible: 1, procID: 0, behind: -1, goAwayFlag:
  2199.              0, refCon: 0);
  2200.  
  2201.  But even if Windows had a NewWindow function that took the same types of
  2202.  arguments as the Mac function, the above code will behave horribly. For one
  2203.  thing, since the Mac Toolbox knows only about Pascal strings, the window
  2204.  title "Untitled" must be converted from a C string (null-terminated string)
  2205.  to a P string (whose first byte contains the strlen) In this code our Mac
  2206.  programmer has used the Lightspeed C '\p' escape, which turns a C string
  2207.  into a P string. On a PC, though, Microsoft C just sees the '\p' as another
  2208.  character.
  2209.  
  2210.  There are Mac programmers who have more experience writing software for
  2211.  other machines (a VAX or Sun workstation), and have therefore a more
  2212.  careful approach to writing portable code. We have seen such programmers
  2213.  write the above function call as follows:
  2214.  
  2215.    WindowPtr w = NewWindow( NIL, &rect, CtoPstr("Untitled"),
  2216.              TRUE,0,-1L,FALSE,0L);
  2217.  
  2218.  These programmers will perhaps become still more careful after the jarring
  2219.  revelation that, in the Windows/Intel environment, not all pointers are
  2220.  created equal and are not interchangeable with long integers. The -1L
  2221.  argument above will throw the rest of the arguments out of phase on the
  2222.  stack. That argument, which is supposed to be a pointer to data, should
  2223.  instead be ((WindowPtr) -1). Likewise, many Motorola and VAX(R)
  2224.  programmers define NIL to be 0L in their standard header file, when it
  2225.  should be ((void *)0) or ((char  *)0).
  2226.  
  2227.  Pointers on Intel CPUs can be 16-bit near pointers, pointing to a location
  2228.  in the current local address space (a memory segment up to 64Kb in size).
  2229.  Or they can be 32-bit far pointers, pointing to a location in some other
  2230.  segment in the global address space (whose size is 20-bits, or 1Mb, on the
  2231.  808x family of processors, but larger on the 80x86 generation of CPUs).
  2232.  
  2233.  Pointers to program code are not necessarily the same size as pointers to
  2234.  program data, depending on a particular convention (called the memory
  2235.  model) which is chosen at compile time. Furthermore a program's static
  2236.  data can exist in a different data segment from the program's automatic
  2237.  variables. Automatic variables are allocated on a program's execution
  2238.  stack, which can be chosen at compile time to exist in a different memory
  2239.  segment from the program's static data.
  2240.  
  2241.  Memory models are defined as follows: small, medium, compact, large, and
  2242.  huge. These names refer to the various possible permutations of near/far
  2243.  pointers as they relate to a program's code and data spaces. For additional
  2244.  details, check out the appropriate section of the C compiler manual you are
  2245.  using for Windows.
  2246.  
  2247.  Before you Mac programmers get too complacent about such things as
  2248.  different-sized pointers, we might point out that, even with the big linear
  2249.  address space of the Motorola chips, Apple has gone to a great deal of
  2250.  trouble to simulate a segmented memory architecture for the Mac, as
  2251.  evidenced by the Segment Loader. Segmented memory makes a lot of sense
  2252.  when you want movable, discardable resources.
  2253.  
  2254.  
  2255.  Saluting Typedef
  2256.  
  2257.  What all this near/far pointer situation means in practice is that, when
  2258.  passing a particular pointer as an argument down a chain of subordinate
  2259.  functions, the routines further down need to be aware of the near/far
  2260.  ancestry of the pointer they have been passed. In some cases it might even
  2261.  be necessary to have two duplicate routines, one of which accepts near
  2262.  pointers as arguments while the other one takes the far variety.
  2263.  
  2264.  In this near/far situation, typedef assumes an importance much greater than
  2265.  its creators may have envisioned. In the original UNIX source code, written
  2266.  for a PDP-11 where ints and pointers were all the same size, typedef is not
  2267.  used very often even with structs.
  2268.  
  2269.  When porting to the more hostile Intel environment, it is critically
  2270.  important to typedef everything, especially pointers. In GENAPP, we were
  2271.  writing APPLCATN.C on the Mac at first, and originally typedef'd our data
  2272.  model as shown in Figure 16.
  2273.  
  2274.  A new data model was then dynamically allocated at run time by a call to
  2275.  env_AllocMem (see Figure 17).
  2276.  
  2277.  On the PC, however, we were using medium model (which defaults to 16-bit
  2278.  near pointers to program data, and 32-bit far pointers to program code). A
  2279.  problem arose because the memory allocation routines, even in medium model,
  2280.  allocate memory objects from the global heap and have to return 32-bit far
  2281.  pointers to these objects. Declaring a pointer to a data model as MODEL *
  2282.  meant that the pointer size defaulted to the 16-bit near size. Our solution
  2283.  was to create a separate type for a pointer to model as follows:
  2284.  
  2285.    typedef MODEL far *PMODEL;
  2286.     /* a 32-bit pointer to data model */
  2287.  
  2288.  The above NewModel routine then had to be revised (see Figure 18).
  2289.  
  2290.  This change produced a ripple effect in our application due to the fact that
  2291.  the data model was used as a collection of graphic objects. The Object
  2292.  Manager section of APPLCATN.C contained routines to manipulate these
  2293.  graphic objects. Many of these routines were passed an OBJECT * as an
  2294.  argument. This code needed to be changed to use the type POBJECT in place of
  2295.  OBJECT *, where POBJECT was declared as follows:
  2296.  
  2297.    typedef OBJECT far * PMODEL;
  2298.    /* a 32-bit pointer to graphic object */
  2299.  
  2300.  In addition to the unsung typedef, there is a newer C language construct
  2301.  that assumes great importance in this near/far pointer situation: function
  2302.  prototyping. Function prototyping is an enhancement to the original C
  2303.  language definition and is part of the proposed ANSI standard.
  2304.  
  2305.  Function prototyping and typedefs are the two most powerful weapons in the
  2306.  war against stray pointer bugs. In GENAPP, the header files APPLCATN.H and
  2307.  ENVRNMT.H contain function prototypes for every function in the program,
  2308.  and were invaluable in bringing both real and potential problems to our
  2309.  attention.
  2310.  
  2311.  
  2312.  Large Model Drawbacks
  2313.  
  2314.  Mac programmers, when encountering these unexpected ripple effects of
  2315.  near/far pointers in an existing program, may feel a sense of delight in
  2316.  discovering the compiler convention called large model, which establishes
  2317.  32-bit pointers to both program code and program data. This closely
  2318.  parallels the Mac; however, there are several strong reasons not to use the
  2319.  large model convention:
  2320.  
  2321.    ■  The large model can cause a significant increase in program size.
  2322.  
  2323.    ■  This increase in size can have a severe impact on program performance.
  2324.  
  2325.    ■  The data segment for static data becomes fixed and unrelocatable,
  2326.       affecting both performance and the possibility of having multiple
  2327.       instances of the program running concurrently.
  2328.  
  2329.    ■  Certain tools and third-party libraries don't support large model (for
  2330.       example, the XVT library is only available in medium model).
  2331.  
  2332.  This just shows that utilizing the closest analogies between two systems may
  2333.  make your port easier in the short run but will not necessarily improve your
  2334.  code.
  2335.  
  2336.    640Kb + 4Mb = 640Kb
  2337.  
  2338.  Ironically the biggest single memory-related problem that Mac programmers
  2339.  will encounter on Windows is not discernible anywhere in the program
  2340.  listing of GENAPP. This obstacle is the 640Kb memory limitation imposed by
  2341.  DOS.
  2342.  
  2343.  Unlike the problems already discussed, this one has no visible effect on our
  2344.  program code (in terms of the language syntax or constructs used). And it
  2345.  can be safely ignored by toy programs like GENAPP. But writers of larger
  2346.  real-world applications on Windows will face this issue time and again.
  2347.  
  2348.  Apple encourages Mac programmers to think of the Toolbox as if it were
  2349.  almost part of the hardware. The mouse tracking software on the Mac is as
  2350.  close to the hardware as you can get without being hardwired into the 68000
  2351.  instruction set. This is why the cursor movement on even an old Mac 128
  2352.  feels smoother and more responsive than the cursor motion on a 386 machine
  2353.  running Windows.
  2354.  
  2355.  Windows is based on lessons learned in part from the Mac, so its API is more
  2356.  elegant and ambitious than the Mac Toolbox. Although its programming
  2357.  interface is better, the resulting programs sometimes have a slightly clunky
  2358.  feel compared to their Mac versions.
  2359.  
  2360.  The Windows environment is, strictly speaking, an application-level
  2361.  program running on top of an operating system which was never designed to
  2362.  support it. The 640Kb that MS-DOS gives you is meant to be shared among the
  2363.  operating system, the windowing environment, your application program's
  2364.  code and data, and the code and data of any other application programs that
  2365.  the user will hope to run at the same time.
  2366.  
  2367.  Mac programmers are used to taking the cover off their machine, putting in
  2368.  4Mb worth of memory chips and having close to 4Mb of memory show up on the
  2369.  Finder About box. On DOS systems, you can take the cover off the machine,
  2370.  put in 4Mb worth of memory chips, and you'll still have less than 640Kb
  2371.  available when you start Windows.
  2372.  
  2373.  Windows/386 helps alleviate much of this problem for non-Windows old apps
  2374.  (such as Lotus(R) 1-2-3(R)), by giving each of them their own 640Kb virtual
  2375.  machine, but ironically Windows apps (such as Meta Software's Design) are
  2376.  stuck having to share the same cramped 640Kb machine with Windows itself.
  2377.  
  2378.  Is it any wonder that long-time DOS users are excited about OS/2, which
  2379.  breaks the 640Kb barrier? Mac programmers may be unaware of the various
  2380.  kludges that let you add memory to your machine without breaking the 640Kb
  2381.  barrier. Expanded memory is a pretty good kludge, and you'll probably need
  2382.  to know about it to get your Mac program running on the PC. The ultimate
  2383.  kludge is the recently unveiled HIMEM.SYS, which goes through major
  2384.  contortions in order to provide DOS with another 64Kb. HIMEM.SYS is a sure
  2385.  sign that DOS is in its death throes. Forward to OS/2 and Presentation
  2386.  Manager!
  2387.  
  2388.  There are several practical ramifications of this particular MS-Windows
  2389.  constraint:
  2390.  
  2391.    ■  The usual space/time trade-off when writing programs no longer holds.
  2392.       Programmers on other systems are used to increasing a program's data
  2393.       requirements in order to improve execution speed (for example, by
  2394.       caching computed results for later use). On Windows, if you increase
  2395.       the run-time size of your application, you may invoke lots of disk
  2396.       swapping and memory shuffling and suffer a huge performance loss.
  2397.  
  2398.    ■  Space is always at a premium, and needs to be considered in all
  2399.       decisions (for example, in deciding whether to use large model or
  2400.       medium model).
  2401.  
  2402.  One advantage of the additional discipline imposed in writing for Windows
  2403.  is that memory-conserving techniques have become more valuable on the Mac
  2404.  with the advent of MultiFinder, as users find that 2Mb is not really very
  2405.  much when you are running several applications. The small tightly coded
  2406.  application is bound to be preferred in any environment over its fatter,
  2407.  slower competitors. In many other ways as well, MultiFinder is helping
  2408.  bring the Macintosh and Windows worlds closer together.
  2409.  
  2410.  
  2411.  You Can Do It!
  2412.  
  2413.  We have presented many details in this article that make it seem as if the
  2414.  path from the Macintosh to Windows is a dangerous one. The large PC market
  2415.  is inviting, but is it worth the difficulties with nonsquare pixels, eight-
  2416.  character filenames, and having to really lock memory handles? The PC world
  2417.  may seem strange compared to the simplicity of the Mac, but it is an
  2418.  important market, and Windows and OS/2 have done a lot to improve the
  2419.  quality of PC computing.
  2420.  
  2421.  Don't be misled by all the minor problems discussed here into thinking that
  2422.  porting Mac programs to the PC is enormously difficult. Our interest in
  2423.  seeing much of the Mac software migrate to the PC, made us want to include
  2424.  as many details as possible.
  2425.  
  2426.  On the other hand, we have only scratched the surface. We covered
  2427.  QuickDraw/GDI, event management, and memory management, and we presented a
  2428.  general approach to porting code. However such topics as printing,
  2429.  scrolling, the clipboard, dialog boxes, text editing, file management, and
  2430.  more are completely missing from this discussion.
  2431.  
  2432.  In some of these areas, the parallels between Windows and the Mac are very
  2433.  close, while in others the differences are sufficiently large that it should
  2434.  be interesting developing a higher-level interface. Finally an additional
  2435.  benefit you will find, is that programming for more than one environment
  2436.  will make you a better programmer in your favorite environment.
  2437.  
  2438.  
  2439.  Figure 1:  Code Fragments from stop_mac.c
  2440.  
  2441.  #include "MacTypes.h"
  2442.  #include "Quickdraw.h"
  2443.                                      ∙
  2444.                                      ∙
  2445.                                      ∙
  2446.  #define SCROLL_BAR_WIDTH    16
  2447.  #define BORDER              5
  2448.  
  2449.  void main()
  2450.  {
  2451.      EventRecord event;
  2452.                                      ∙
  2453.                                      ∙
  2454.                                      ∙
  2455.      switch (myEvent.what)
  2456.      {
  2457.          case updateEvt:
  2458.              BeginUpdate((WindowPtr) event.message);
  2459.              StopSign((GrafPtr) event.message);
  2460.              EndUpdate((WindowPtr) event.message);
  2461.              break;
  2462.                                      ∙
  2463.                                      ∙
  2464.                                      ∙
  2465.      }
  2466.  }
  2467.  
  2468.  void StopSign(port)
  2469.      GrafPtr port;
  2470.  {
  2471.      GrafPtr oldPort;
  2472.      Rect r;
  2473.      Point oldOrigin;
  2474.      short scale;
  2475.  
  2476.      GetPort(&oldPort);
  2477.      SetPort(port);
  2478.      r = port->portRect;         /* structure copy */
  2479.      r.bottom -= ((2 * BORDER) + SCROLL_BAR_WIDTH);
  2480.      r.right -= ((2 * BORDER) + SCROLL_BAR_WIDTH);
  2481.      scale = min(r.bottom - r.top, r.right - r.left) / 18;
  2482.  
  2483.      oldOrigin = topLeft(port->portRect);
  2484.      SetOrigin(oldOrigin.h + BORDER, oldOrigin.v + BORDER);
  2485.  
  2486.      MoveTo(5 * scale, 0);
  2487.      Line(8 * scale, 0);
  2488.      Line(5 * scale, 5  * scale);
  2489.      Line(0, 8  * scale);
  2490.      Line(-5  * scale, 5  * scale);
  2491.      Line(-8  * scale, 0);
  2492.      Line(-5  * scale, -5  * scale);
  2493.      Line(0, -8  * scale);
  2494.      Line(5 * scale, -5  * scale);
  2495.  
  2496.      MoveTo(0, 5  * scale);
  2497.      Line(18  * scale, 0);
  2498.      MoveTo(0, 13  * scale);
  2499.      Line(18  * scale, 0);
  2500.  
  2501.      MoveTo(4  * scale, 7  * scale);
  2502.      Line(-2  * scale, 0);
  2503.      Line(0, 2  * scale);
  2504.      Line(2  * scale, 0);
  2505.      Line(0, 2  * scale);
  2506.      Line(-2  * scale, 0);
  2507.  
  2508.      MoveTo(7  * scale, 7  * scale);
  2509.      Line(0, 4  * scale);
  2510.      Move(-1  * scale, -4  * scale);
  2511.      Line(2  * scale, 0);
  2512.  
  2513.      MoveTo(10  * scale, 7  * scale);
  2514.      Line(2  * scale, 0);
  2515.      Line(0, 4  * scale);
  2516.      Line(-2  * scale, 0);
  2517.      Line(0, -4  * scale);
  2518.  
  2519.      MoveTo(14  * scale, 7  * scale);
  2520.      Line(0, 4  * scale);
  2521.      Move(0, -4  * scale);
  2522.      Line(2  * scale, 0);
  2523.      Line(0, 2  * scale);
  2524.      Line(-2  * scale, 0);
  2525.  
  2526.      SetOrigin(oldOrigin.h, oldOrigin.v);
  2527.      SetPort(oldPort);
  2528.  }
  2529.  
  2530.  
  2531.  Figure 3:  Take One of Windows Version
  2532.  
  2533.  /* from HELLO.C */
  2534.                                      ∙
  2535.                                      ∙
  2536.                                      ∙
  2537.       case WM_PAINT:
  2538.            BeginPaint(hWnd, (LPPAINTSTRUCT) &ps);
  2539.  #ifdef OLD_CODE
  2540.            HelloPaint(ps.hdc);
  2541.  #else
  2542.            StopSign(hWnd, ps.hdc);
  2543.  #endif
  2544.            EndPaint(hWnd, (LPPAINTSTRUCT) &ps);
  2545.  ......................................................................
  2546.  
  2547.  /* STOP.C */
  2548.  
  2549.  #include "windows.h"
  2550.  
  2551.  #define BORDER 5
  2552.  
  2553.  BOOL FAR PASCAL Move(HDC hDC, int x, int y)
  2554.  {
  2555.      unsigned long curr = GetCurrentPosition(hDC);
  2556.      return MoveTo(hDC, LOWORD(curr)+x, HIWORD(curr)+y);
  2557.  }
  2558.  
  2559.  BOOL FAR PASCAL Line(HDC hDC, int x, int y)
  2560.  {
  2561.      unsigned long curr = GetCurrentPosition(hDC);
  2562.      return LineTo(hDC, LOWORD(curr)+x, HIWORD(curr)+y);
  2563.  }
  2564.  
  2565.  void StopSign(HWND hWnd, HDC hDC)
  2566.  {
  2567.       RECT r;
  2568.       int scale;
  2569.  
  2570.       GetClientRect(hWnd, &r);
  2571.       r.bottom -= (2 * BORDER);
  2572.       r.right  -= (2 * BORDER);
  2573.       scale = min(r.bottom - r.top, r.right - r.left) / 18;
  2574.  
  2575.       OffsetViewportOrg(hDC, BORDER, BORDER);
  2576.  
  2577.      MoveTo(hDC, 5 * scale, 0);
  2578.      Line  (hDC, 8 * scale, 0);
  2579.      Line  (hDC, 5 * scale, 5  * scale);
  2580.                                      ∙
  2581.                                      ∙
  2582.                                      ∙
  2583.      Move(hDC, 0, -4  * scale);
  2584.      Line(hDC, 2  * scale, 0);
  2585.      Line(hDC, 0, 2  * scale);
  2586.      Line(hDC, -2  * scale, 0);
  2587.  
  2588.       OffsetViewportOrg(hDC, -BORDER, -BORDER);
  2589.  }
  2590.  
  2591.  
  2592.  Figure 5:  Take Two of Windows Version
  2593.  
  2594.  /* from STOP.C */
  2595.  
  2596.  void StopSign(HWND hWnd, HDC hDC)
  2597.  {
  2598.      RECT r;
  2599.      int scale;
  2600.  
  2601.      GetClientRect(hWnd, &r);
  2602.      r.bottom -= (2 * BORDER);
  2603.      r.right  -= (2 * BORDER);
  2604.  #ifdef USE_MM_TEXT
  2605.      scale = min(r.bottom - r.top, r.right - r.left) / 18;
  2606.  #else
  2607.      scale = 1;
  2608.      SetMapMode(hDC, MM_ISOTROPIC);
  2609.      SetWindowExt(hDC, 18, 18);
  2610.      SetViewportExt(hDC, r.right, r.bottom);
  2611.  #endif
  2612.                                      ∙
  2613.                                      ∙
  2614.                                      ∙
  2615.  }
  2616.  
  2617.  
  2618.  Figure 6:  Take Three of Windows Version
  2619.  
  2620.  /* from HELLO.C */
  2621.  
  2622.  RECT client_rect = {0};
  2623.                                      ∙
  2624.                                      ∙
  2625.                                      ∙
  2626.      case WM_PAINT:
  2627.          BeginPaint(hWnd, (LPPAINTSTRUCT) &ps);
  2628.          StopSign(ps.hdc);
  2629.          EndPaint(hWnd, (LPPAINTSTRUCT) &ps);
  2630.          break;
  2631.                                      ∙
  2632.                                      ∙
  2633.                                      ∙
  2634.      case WM_SIZE:
  2635.          /*
  2636.              more efficient way:
  2637.                  client_rect.bottom = HIWORD(lParam);
  2638.                  client_rect.right = LOWORD(lParam);
  2639.          */
  2640.          GetClientRect(hWnd, &client_rect);
  2641.          break;
  2642.  .....................................................................
  2643.  
  2644.  /* from STOP.C */
  2645.  
  2646.  void StopSign(HDC hDC)             /* NEW CODE */
  2647.  {
  2648.      extern RECT client_rect;
  2649.      RECT r;
  2650.      int scale;
  2651.  
  2652.      r = client_rect;        /* structure copy */
  2653.  
  2654.      r.bottom -= (2 * BORDER);
  2655.      r.right  -= (2 * BORDER);
  2656.                                      ∙
  2657.                                      ∙
  2658.                                      ∙
  2659.  
  2660.  
  2661.  Figure 7:  Windows Version, Take Four
  2662.  
  2663.  /* DRAW.H */
  2664.  
  2665.  /* external interface to draw.c */
  2666.  extern BOOL FAR PASCAL Move(HDC hDC, unsigned x, unsigned y);
  2667.  extern BOOL FAR PASCAL Line(HDC hDC, unsigned x, unsigned y);
  2668.  extern BOOL FAR PASCAL MyMoveTo(HDC hDC, unsigned x, unsigned y);
  2669.  extern BOOL FAR PASCAL MyLineTo(HDC hDC, unsigned x, unsigned y);
  2670.  
  2671.  #ifndef DRAW_C
  2672.  #define MoveTo(hDC,x,y)       MyMoveTo((hDC),(x),(y))
  2673.  #define LineTo(hDC,x,y)       MyLineTo((hDC),(x),(y))
  2674.  #endif
  2675.  
  2676.  .....................................................................
  2677.  
  2678.  /* DRAW.C */
  2679.  
  2680.  #include "windows.h"
  2681.  #define DRAW_C
  2682.  #include "draw.h"
  2683.  
  2684.  /* not visible outside this module */
  2685.  static unsigned curr_x=0, curr_y=0;
  2686.  
  2687.  BOOL FAR PASCAL Move(HDC hDC, unsigned x, unsigned y)
  2688.  {
  2689.       curr_x += x;
  2690.       curr_y += y;
  2691.       return MoveTo(hDC, curr_x, curr_y);
  2692.  }
  2693.  
  2694.  BOOL FAR PASCAL Line(HDC hDC, unsigned x, unsigned y)
  2695.  {
  2696.       curr_x += x;
  2697.       curr_y += y;
  2698.       return LineTo(hDC, curr_x, curr_y);
  2699.  }
  2700.  
  2701.  BOOL FAR PASCAL MyMoveTo(HDC hDC, unsigned x, unsigned y)
  2702.  {
  2703.       curr_x = x;
  2704.       curr_y = y;
  2705.       return MoveTo(hDC, x, y);
  2706.  }
  2707.  
  2708.  BOOL FAR PASCAL MyLineTo(HDC hDC, unsigned x, unsigned y)
  2709.  {
  2710.       curr_x = x;
  2711.       curr_y = y;
  2712.       return LineTo(hDC, x, y);
  2713.  }
  2714.  
  2715.  
  2716.  Figure 8:  Portable Version of Stop Sign
  2717.  
  2718.  /* from HELLO.C */
  2719.                   <...>
  2720.  #include "canvas.h"
  2721.                   <...>
  2722.      /* in WinMain, after call to CreateWindow() */
  2723.      new_canvas((WINDOW) hWnd);
  2724.                   <...>
  2725.      /* in HelloWndProc... */
  2726.      HCANVAS hCanvas;
  2727.                   <...>
  2728.      case WM_PAINT:
  2729.  #if 1
  2730.          hCanvas = find_canvas((WINDOW) hWnd);
  2731.          begin_update(hCanvas);
  2732.          StopSign(hCanvas);
  2733.          end_update(hCanvas);
  2734.  #else
  2735.          BeginPaint( hWnd, (LPPAINTSTRUCT)&ps );
  2736.          StopSign(ps.hdc);
  2737.          EndPaint( hWnd, (LPPAINTSTRUCT)&ps );
  2738.  #endif
  2739.          break;
  2740.  
  2741.      case WM_SIZE:
  2742.          resize(find_canvas((WINDOW) hWnd),
  2743.              LOWORD(lParam), HIWORD(lParam));
  2744.          break;
  2745.  
  2746.  ......................................................................
  2747.  
  2748.  /* from MACHELLO.C */
  2749.  ...
  2750.  #define MAC
  2751.  #include "canvas.h"
  2752.                   <...>
  2753.      /* in main(), after call to NewWindow() */
  2754.      new_canvas((WINDOW) windowptr);
  2755.                   <...>
  2756.      case updateEvt:
  2757.          hCanvas = find_canvas(event.message);
  2758.          begin_update(hCanvas);
  2759.          StopSign(hCanvas);
  2760.          end_update(hCanvas);
  2761.          break;
  2762.  
  2763.  ......................................................................
  2764.  
  2765.  /* STOP.C-uses CANVAS facility */
  2766.  
  2767.  /* no windows.h */
  2768.  /* no MacTypes.h, no QuickDraw.h */
  2769.  #include "canvas.h"
  2770.  
  2771.  #define BORDER      5
  2772.  
  2773.  #define min(a,b)    ((a)<(b)?(a):(b))
  2774.  
  2775.  void StopSign(HCANVAS hCanvas)
  2776.  {
  2777.      short width, depth;
  2778.  
  2779.      get_size(hCanvas, &width, &depth);
  2780.  
  2781.      width -= (2 * BORDER);
  2782.      depth -= (2 * BORDER);
  2783.  
  2784.      set_scale(hCanvas, width, depth, 18);
  2785.      offset_org(hCanvas, BORDER, BORDER);
  2786.  
  2787.      move_to(hCanvas, 5, 0);
  2788.      line(hCanvas, 8, 0);
  2789.      line(hCanvas, 5, 5);
  2790.                                      ∙
  2791.                                      ∙
  2792.                                      ∙
  2793.      move(hCanvas, 0, -4);
  2794.      line(hCanvas, 2, 0);
  2795.      line(hCanvas, 0, 2);
  2796.      line(hCanvas, -2, 0);
  2797.  
  2798.      offset_org(hCanvas, -BORDER, -BORDER);
  2799.  }
  2800.  
  2801.  
  2802.  Figure 9:  Some CANVAS Routines, Extracted from ENVRNMT.C
  2803.  
  2804.  /* canvas.h */
  2805.  
  2806.  typedef unsigned HCANVAS;   /* handle to canvas */
  2807.  
  2808.  #ifdef MAC
  2809.  typedef void *WINDOW;           /* WindowPtr */
  2810.  typedef void *HPIC;             /* PicHandle */
  2811.  #define NEAR  /**/
  2812.  #else
  2813.  typedef unsigned short WINDOW;  /* HWND */
  2814.  typedef unsigned short HPIC;    /* HANDLE */
  2815.  #endif
  2816.  
  2817.  HCANVAS new_canvas(WINDOW window);
  2818.  HCANVAS find_canvas(WINDOW window);
  2819.  void kill_canvas(HCANVAS hCanvas);
  2820.  void move(HCANVAS hCanvas, short x, short y);
  2821.  void line(HCANVAS hCanvas, short x, short y);
  2822.  void move_to(HCANVAS hCanvas, short x, short y);
  2823.  void line_to(HCANVAS hCanvas, short x, short y);
  2824.  void get_xy(HCANVAS hCanvas, short *x, short *y);
  2825.  void resize(HCANVAS hCanvas, short x, short y);
  2826.  void get_size(HCANVAS hCanvas, short *x, short *y);
  2827.  void offset_org(HCANVAS hCanvas, short x, short y);
  2828.  void set_scale(HCANVAS hCanvas, short x, short y, short units);
  2829.  void begin_update(HCANVAS hCanvas);
  2830.  void end_update(HCANVAS hCanvas);
  2831.  void debug(char *mask, ...);
  2832.  void open_picture(HCANVAS hCanvas);
  2833.  void close_picture(HCANVAS hCanvas, HPIC *hPic);
  2834.  void draw_picture(HCANVAS hCanvas, HPIC hPic);
  2835.  void kill_picture(HPIC hPic);
  2836.  
  2837.  #define NUM_CANVASES        10
  2838.  
  2839.  #define ILLEGAL_HCANVAS     0xFFFF
  2840.  #define ILLEGAL_WINDOW      0
  2841.  
  2842.  .....................................................................
  2843.  /* canvas.c-abridged version of envrnmt.c */
  2844.  /* just contains graphics-related routines for Mac and Windows */
  2845.  /* Andrew Schulman  20-Oct-1988 */
  2846.  /* revised by Ray Valdes 23-Oct-1988 */
  2847.  
  2848.  #ifdef MAC
  2849.  #include <unix.h>
  2850.  #include "Quickdraw.h"
  2851.  #include "WindowMgr.h"
  2852.  #else
  2853.  #include <memory.h>
  2854.  #include "windows.h"
  2855.  #endif
  2856.  
  2857.  #include "canvas.h"
  2858.  
  2859.  typedef struct {
  2860.      WINDOW window;
  2861.      short x,y;
  2862.      HPIC pic;
  2863.  #ifdef MAC
  2864.      WindowRecord wRecord;
  2865.      short scale;
  2866.  #else
  2867.      PAINTSTRUCT ps;             /* includes HDC */
  2868.      short right,bottom;
  2869.  #endif
  2870.      unsigned long user_data;
  2871.      } CANVAS;
  2872.  
  2873.  /* macros to simplify access to the environment block */
  2874.  #define CANVAS_RIGHT(hCanvas)     (canvas[hCanvas].right)
  2875.  #define CANVAS_BOTTOM(hCanvas)    (canvas[hCanvas].bottom)
  2876.  #define CANVAS_X(hCanvas)         (canvas[hCanvas].x)
  2877.  #define CANVAS_Y(hCanvas)         (canvas[hCanvas].y)
  2878.  #define CANVAS_USERDATA(hCanvas)  (canvas[hCanvas].user_data)
  2879.  #define CANVAS_WINDOW(hCanvas)
  2880.  (canvas[hCanvas].window)
  2881.  #define CANVAS_PIC(hCanvas)       (canvas[hCanvas].pic)
  2882.  #ifdef MAC
  2883.  #define CANVAS_GP(hCanvas)        ((GrafPtr)
  2884.  CANVAS_WINDOW(hCanvas))
  2885.  #define CANVAS_WP(hCanvas)        ((WindowPtr)
  2886.  CANVAS_WINDOW(hCanvas))
  2887.  #define CANVAS_SCALE(hCanvas)     (canvas[hCanvas].scale)
  2888.  #define CANVAS_PORTRECT(hCanvas)  (CANVAS_WP(hCanvas)-
  2889.  >portRect)
  2890.  #else
  2891.  #define CANVAS_PAINTSTR(hCanvas)  (canvas[hCanvas].ps)
  2892.  #define CANVAS_HDC(hCanvas)
  2893.  (CANVAS_PAINTSTR(hCanvas).hdc)
  2894.  #define CANVAS_HWND(hCanvas)      ((HWND)
  2895.  CANVAS_WINDOW(hCanvas))
  2896.  #endif
  2897.  
  2898.  CANVAS canvas[NUM_CANVASES] = {0};
  2899.  
  2900.  /****************************************************************/
  2901.  static void NEAR valid_canvas(hCanvas)
  2902.      HCANVAS hCanvas;
  2903.  {
  2904.      if (hCanvas >= NUM_CANVASES)
  2905.          debug("No such hCanvas: %u", hCanvas);
  2906.  #ifndef MAC
  2907.      /* Windows lets us do a little more error checking */
  2908.      if (! IsWindow(CANVAS_HWND(hCanvas)))
  2909.          debug("Illegal hWnd %u in hCanvas %u",
  2910.  CANVAS_HWND(hCanvas), hCanvas);
  2911.  #endif
  2912.  }
  2913.  
  2914.  /***************************************************************/
  2915.  HCANVAS new_canvas(window)
  2916.      WINDOW window;
  2917.  {
  2918.      register HCANVAS hCanvas;
  2919.      for (hCanvas=0; hCanvas < NUM_CANVASES; hCanvas++)
  2920.          if (CANVAS_WINDOW(hCanvas) == ILLEGAL_WINDOW)
  2921.          {
  2922.              CANVAS_WINDOW(hCanvas) = window;
  2923.              return hCanvas;
  2924.          }
  2925.      /* still here */
  2926.      return ILLEGAL_HCANVAS;
  2927.  }
  2928.  
  2929.  /* this is just temporary until we have application event queue */
  2930.  HCANVAS find_canvas(window)
  2931.      WINDOW window;
  2932.  {
  2933.      register HCANVAS hCanvas;
  2934.      for (hCanvas=0; hCanvas < NUM_CANVASES; hCanvas++)
  2935.          if (CANVAS_WINDOW(hCanvas) == window)
  2936.              return hCanvas;
  2937.      /* still here */
  2938.      return ILLEGAL_HCANVAS;
  2939.  }
  2940.  
  2941.  /*******************************************************************/
  2942.  #ifdef MAC
  2943.  #define memset(a,b,c)       setmem((a),(c),(b))
  2944.  #endif
  2945.  
  2946.  void kill_canvas(hCanvas)
  2947.      HCANVAS hCanvas;
  2948.  {
  2949.      valid_canvas(hCanvas);
  2950.      /* set all fields to zero */
  2951.      memset(&canvas[hCanvas], 0, sizeof(CANVAS));
  2952.  }
  2953.  
  2954.  /*******************************************************************/
  2955.  /* since Move,Line,MoveTo,LineTo are so similar, write them with macros */
  2956.  #ifdef MAC
  2957.  #define MAYBE_SWITCHING_PORT(hCanvas,FUNC,x,y)      \
  2958.  {                                                   \
  2959.      if (CANVAS_GP(hCanvas) != thePort)              \
  2960.      {                                               \
  2961.  
  2962.          GrafPtr oldport = thePort;                  \
  2963.          SetPort(CANVAS_GP(hCanvas));                \
  2964.          FUNC(x * CANVAS_SCALE(hCanvas),             \
  2965.              y * CANVAS_SCALE(hCanvas));             \
  2966.          SetPort(oldport);                           \
  2967.      }                                               \
  2968.      else                                            \
  2969.          FUNC(x,y);                                  \
  2970.  }
  2971.  #else
  2972.  #define UPDATING_XY(hCanvas,FUNC,OP,x,y)            \
  2973.  {                                                   \
  2974.      if (! CANVAS_HDC(hCanvas))                      \
  2975.          return;                                     \
  2976.      CANVAS_X(hCanvas) OP x;                         \
  2977.      CANVAS_Y(hCanvas) OP y;                         \
  2978.      FUNC(CANVAS_HDC(hCanvas),                       \
  2979.          CANVAS_X(hCanvas), CANVAS_Y(hCanvas));      \
  2980.  }
  2981.  #endif
  2982.  
  2983.  /*******************************************************************/
  2984.  void move(hCanvas, x, y)
  2985.      HCANVAS hCanvas;
  2986.      short x,y;
  2987.  {
  2988.      valid_canvas(hCanvas);
  2989.  #ifdef MAC
  2990.      MAYBE_SWITCHING_PORT(hCanvas, Move, x, y);
  2991.  #else
  2992.      UPDATING_XY(hCanvas, MoveTo, +=, x, y);
  2993.  #endif
  2994.  }
  2995.  
  2996.  void line(hCanvas, x, y)
  2997.      HCANVAS hCanvas;
  2998.      short x,y;
  2999.  {
  3000.      valid_canvas(hCanvas);
  3001.  #ifdef MAC
  3002.      MAYBE_SWITCHING_PORT(hCanvas, Line, x, y);
  3003.  #else
  3004.      UPDATING_XY(hCanvas, LineTo, +=, x, y);
  3005.  #endif
  3006.  }
  3007.  
  3008.  void move_to(hCanvas, x, y)
  3009.      HCANVAS hCanvas;
  3010.      short x,y;
  3011.  {
  3012.      valid_canvas(hCanvas);
  3013.  #ifdef MAC
  3014.      MAYBE_SWITCHING_PORT(hCanvas,MoveTo,x,y);
  3015.  #else
  3016.      UPDATING_XY(hCanvas, MoveTo, =, x, y);
  3017.  #endif
  3018.  }
  3019.  
  3020.  void line_to(hCanvas, x, y)
  3021.      HCANVAS hCanvas;
  3022.      short x,y;
  3023.  {
  3024.      valid_canvas(hCanvas);
  3025.  #ifdef MAC
  3026.      MAYBE_SWITCHING_PORT(hCanvas,LineTo,x,y);
  3027.  #else
  3028.      UPDATING_XY(hCanvas, LineTo, =, x, y);
  3029.  #endif
  3030.  }
  3031.  
  3032.  void get_xy(hCanvas, x, y)
  3033.      HCANVAS hCanvas;
  3034.      short *x, *y;
  3035.  {
  3036.      valid_canvas(hCanvas);
  3037.  #ifdef MAC
  3038.  
  3039.      *x = CANVAS_GP(hCanvas)->pnLoc.h *
  3040.  CANVAS_SCALE(hCanvas);
  3041.      *y = CANVAS_GP(hCanvas)->pnLoc.v *
  3042.  CANVAS_SCALE(hCanvas);
  3043.  #else
  3044.      *x = CANVAS_X(hCanvas);
  3045.      *y = CANVAS_Y(hCanvas);
  3046.  #endif
  3047.  }
  3048.  
  3049.  /*******************************************************************/
  3050.  #ifdef MAC
  3051.  #define SCROLL_BAR_WIDTH        16
  3052.  #endif
  3053.  
  3054.  void resize(hCanvas, x, y)
  3055.      HCANVAS hCanvas;
  3056.      short x,y;
  3057.  {
  3058.      valid_canvas(hCanvas);
  3059.  #ifdef MAC
  3060.      /* nothing */
  3061.  #else
  3062.      CANVAS_RIGHT(hCanvas) = x;
  3063.      CANVAS_BOTTOM(hCanvas) = y;
  3064.  #endif
  3065.  }
  3066.  
  3067.  void get_size(hCanvas, x, y)
  3068.      HCANVAS hCanvas;
  3069.      short *x, *y;
  3070.  {
  3071.      valid_canvas(hCanvas);
  3072.  #ifdef MAC
  3073.      {
  3074.          Rect r;
  3075.          r = CANVAS_PORTRECT(hCanvas);  /* structure copy */
  3076.          *x = (r.right - r.left) - SCROLL_BAR_WIDTH;
  3077.          *y = (r.bottom - r.top) - SCROLL_BAR_WIDTH;
  3078.      }
  3079.  #else
  3080.      *x = CANVAS_RIGHT(hCanvas);
  3081.      *y = CANVAS_BOTTOM(hCanvas);
  3082.  #endif
  3083.  }
  3084.  
  3085.  void offset_org(hCanvas, x, y)
  3086.      HCANVAS hCanvas;
  3087.      short x,y;
  3088.  {
  3089.      valid_canvas(hCanvas);
  3090.  #ifdef MAC
  3091.      if (CANVAS_GP(hCanvas) != thePort)
  3092.      {
  3093.          GrafPtr oldport = thePort;      /* or use GetPort */
  3094.          SetPort(CANVAS_GP(hCanvas));
  3095.          SetOrigin(CANVAS_PORTRECT(hCanvas).left + x,
  3096.              CANVAS_PORTRECT(hCanvas).top + y);
  3097.          SetPort(oldport);
  3098.      }
  3099.      else
  3100.          SetOrigin(CANVAS_PORTRECT(hCanvas).left + x,
  3101.              CANVAS_PORTRECT(hCanvas).top + y);
  3102.  #else
  3103.      if (! CANVAS_HDC(hCanvas))
  3104.          return;
  3105.      OffsetViewportOrg(CANVAS_HDC(hCanvas), x, y);
  3106.  #endif
  3107.  }
  3108.  
  3109.  void set_scale(hCanvas, x, y, units)
  3110.      HCANVAS hCanvas;
  3111.      short x,y,units;
  3112.  {
  3113.      valid_canvas(hCanvas);
  3114.  #ifdef MAC
  3115.      CANVAS_SCALE(hCanvas) = min(x,y) / units;
  3116.  #else
  3117.  
  3118.      if (! CANVAS_HDC(hCanvas))
  3119.          return;
  3120.      SetMapMode(CANVAS_HDC(hCanvas), MM_ISOTROPIC);
  3121.      SetWindowExt(CANVAS_HDC(hCanvas), units, units);
  3122.      SetViewportExt(CANVAS_HDC(hCanvas), x, y);
  3123.  #endif
  3124.  }
  3125.  
  3126.  /*******************************************************************/
  3127.  void begin_update(hCanvas)
  3128.      HCANVAS hCanvas;
  3129.  {
  3130.      valid_canvas(hCanvas);
  3131.  #ifdef MAC
  3132.      BeginUpdate(CANVAS_WP(hCanvas));
  3133.  #else
  3134.      BeginPaint(CANVAS_HWND(hCanvas), &CANVAS_PAINTSTR(hCanvas));
  3135.      /* CANVAS_HDC() is set via CANVAS_PAINTSTR() */
  3136.  #endif
  3137.  }
  3138.  
  3139.  void end_update(hCanvas)
  3140.      HCANVAS hCanvas;
  3141.  {
  3142.      valid_canvas(hCanvas);
  3143.  #ifdef MAC
  3144.      EndUpdate(CANVAS_WP(hCanvas));
  3145.      DrawGrowIcon(CANVAS_WP(hCanvas));
  3146.  #else
  3147.      EndPaint(CANVAS_HWND(hCanvas), &CANVAS_PAINTSTR(hCanvas));
  3148.      CANVAS_HDC(hCanvas) = 0;        /* important! */
  3149.  #endif
  3150.  }
  3151.  
  3152.  /*******************************************************************/
  3153.  void open_picture(hCanvas)
  3154.      HCANVAS hCanvas;
  3155.  {
  3156.      valid_canvas(hCanvas);
  3157.  #ifdef MAC
  3158.      CANVAS_PIC(hCanvas) = (HPIC) OpenPicture(CANVAS_PORTRECT(hCanvas));
  3159.  #else
  3160.      CANVAS_PIC(hCanvas) = CANVAS_HDC(hCanvas);      /* save old DC */
  3161.      CANVAS_HDC(hCanvas) = CreateMetaFile(NULL);
  3162.  #endif
  3163.  }
  3164.  
  3165.  void close_picture(hCanvas, hPic)
  3166.      HCANVAS hCanvas;
  3167.      HPIC *hPic;
  3168.  {
  3169.      valid_canvas(hCanvas);
  3170.  #ifdef MAC
  3171.      ClosePicture();
  3172.      *hPic = CANVAS_PIC(hCanvas);
  3173.  #else
  3174.      *hPic = CloseMetaFile(CANVAS_HDC(hCanvas));
  3175.      CANVAS_HDC(hCanvas) = CANVAS_PIC(hCanvas);     /* restore old DC */
  3176.  #endif
  3177.  }
  3178.  
  3179.  void draw_picture(hCanvas, hPic)
  3180.      HCANVAS hCanvas;
  3181.      HPIC hPic;
  3182.  {
  3183.      valid_canvas(hCanvas);
  3184.  #ifdef MAC
  3185.      /* perhaps use this for ANISOTROPIC scaling */
  3186.      /* may have to switch grafPorts back and forth */
  3187.      DrawPicture(hPic, CANVAS_PORTRECT(hCanvas));
  3188.  #else
  3189.      PlayMetaFile(CANVAS_HDC(hCanvas), hPic);
  3190.  #endif
  3191.  }
  3192.  
  3193.  void kill_picture(hPic)
  3194.      HPIC hPic;
  3195.  {
  3196.  #ifdef MAC
  3197.      KillPicture(hPic);
  3198.  #else
  3199.      DeleteMetaFile(hPic);
  3200.  #endif
  3201.  }
  3202.  
  3203.  /*******************************************************************/
  3204.  
  3205.  #include <stdio.h>
  3206.  #ifdef MAC
  3207.  typedef char *va_list;
  3208.  #define va_start(ap,v)      ap = (va_list)&v + sizeof(v)
  3209.  #else
  3210.  #include <stdarg.h>
  3211.  #endif
  3212.  
  3213.  void debug(mask)
  3214.      char *mask;
  3215.  {
  3216.      char buf[255];
  3217.      va_list args;
  3218.  
  3219.      va_start(args, mask);
  3220.      vsprintf(buf, mask, args);
  3221.  #ifdef MAC
  3222.      ParamText(CtoPStr(buf), 0L, 0L, 0L);
  3223.      StopAlert(RESOURCE_ID, 0L);
  3224.  #else
  3225.      MessageBox(GetActiveWindow(), buf, "Debug!", MB_OK);
  3226.  #endif
  3227.  }
  3228.  
  3229.  
  3230.  Figure 10:  Using Pictures and Multiple Windows
  3231.  
  3232.  /* from Windows HELLO.C */
  3233.                                      ∙
  3234.                                      ∙
  3235.                                      ∙
  3236.  static int closed_canvases=0;
  3237.                                      ∙
  3238.                                      ∙
  3239.                                      ∙
  3240.      char buf[80];
  3241.      register int i;
  3242.      for (i=0; i<NUM_CANVASES; i++)
  3243.      {
  3244.          sprintf(buf, "Window #%u", i+1);
  3245.          hWnd = CreateWindow((LPSTR)szAppName,
  3246.                              (LPSTR)buf,
  3247.                              WS_TILEDWINDOW,
  3248.                              CW_USEDEFAULT,0,CW_USEDEFAULT,0,
  3249.                              (HWND)NULL,        /* no parent */
  3250.                              (HMENU)NULL,       /* use class menu */
  3251.                              (HANDLE)hInstance, /* handle to window instance *
  3252.                              (LPSTR)NULL        /* no params to pass on */
  3253.                              );
  3254.  
  3255.          (void) new_canvas((unsigned long) hWnd);
  3256.                                      ∙
  3257.                                      ∙
  3258.                                      ∙
  3259.      }
  3260.                                      ∙
  3261.                                      ∙
  3262.                                      ∙
  3263.      case WM_DESTROY:
  3264.          kill_canvas(find_canvas((unsigned long) hWnd));
  3265.          closed_canvases++;
  3266.          if (closed_canvases == NUM_CANVASES)
  3267.          {
  3268.              KillStopSign();
  3269.              PostQuitMessage(0);
  3270.          }
  3271.          break;
  3272.                                      ∙
  3273.                                      ∙
  3274.                                      ∙
  3275.  ...................................................................
  3276.  
  3277.  /* from STOP.C-version uses pictures */
  3278.                                      ∙
  3279.                                      ∙
  3280.                                      ∙
  3281.  static HPIC hPic=0;
  3282.  
  3283.  void StopSign(HCANVAS hCanvas)
  3284.  {
  3285.                                      ∙
  3286.                                      ∙
  3287.                                      ∙
  3288.      if (! hPic)
  3289.      {
  3290.          open_picture(hCanvas);
  3291.          move_to(hCanvas, 5, 0);
  3292.          line(hCanvas, 8, 0);
  3293.                                      ∙
  3294.                                      ∙
  3295.                                      ∙
  3296.          line(hCanvas, -2, 0);
  3297.          close_picture(hCanvas, &hPic);
  3298.          debug("picture created: %u", hPic);
  3299.      }
  3300.  
  3301.      draw_picture(hCanvas, hPic);
  3302.                                      ∙
  3303.                                      ∙
  3304.                                      ∙
  3305.  }
  3306.  
  3307.  void KillStopSign()
  3308.  {
  3309.      kill_picture(hPic);
  3310.      debug("picture killed: %u", hPic);
  3311.  }
  3312.  
  3313.  
  3314.  Figure 11:  Macintosh EventRecord vs. Windows MSG
  3315.  
  3316.  /* from EventMgr.h */
  3317.  typedef struct {
  3318.      int     what;
  3319.      long    message;
  3320.      long    when;
  3321.      Point   where;
  3322.      int     modifiers;
  3323.      } EventRecord;
  3324.  
  3325.  /* from windows.h */
  3326.  typedef struct tagMSG {
  3327.      HWND  hwnd;
  3328.      WORD  message;
  3329.      WORD  wParam;
  3330.      LONG  lParam;
  3331.      DWORD time;
  3332.      POINT pt;
  3333.      } MSG;
  3334.  
  3335.  
  3336.  Figure 12:  Environment-Independent Memory Functions
  3337.  
  3338.  NULL_EVENT
  3339.  QUIT_EVENT
  3340.  CMD_EVENT
  3341.  MOUSEDOWN_EVENT
  3342.  MOUSEUP_EVENT
  3343.  OPENVIEW_EVENT
  3344.  CLOSEVIEW_EVENT
  3345.  MOVEVIEW_EVENT
  3346.  RESIZEVIEW_EVENT
  3347.  ACTIVATEVIEW_EVENT
  3348.  UPDATEVIEW_EVENT
  3349.  CHAR_EVENT
  3350.  
  3351.  
  3352.  Figure 13:  Environment-independent memory allocation functions from
  3353.              ENVRNMT.C.
  3354.  
  3355.  #ifdef MAC
  3356.  typedef char **     Handle; /* Macintosh handle */
  3357.  typedef char *      MEMPTR;
  3358.  typedef MEMPTR *    MEMBLOCK;
  3359.  #else
  3360.  typedef short       HANDLE; /* Windows handle */
  3361.  typedef char far *  MEMPTR;
  3362.  typedef HANDLE      MEMBLOCK;
  3363.  #endif
  3364.  typedef long        MEMSIZE;
  3365.  
  3366.  MEMBLOCK    env_AllocMem(size)
  3367.  MEMSIZE     size;
  3368.  {
  3369.  #ifdef MAC
  3370.      return (MEMBLOCK) NewHandle((Size) size);
  3371.  #else
  3372.      return GlobalAlloc(GMEM_MOVEABLE, size);
  3373.  #endif
  3374.  }
  3375.  
  3376.  VOID        env_ReAllocMem(handle,size)
  3377.  MEMBLOCK *  handle;
  3378.  MEMSIZE     size;
  3379.  {
  3380.  #ifdef MAC
  3381.      SetHandleSize((Handle)*handle,(Size)size);
  3382.  #else
  3383.      *handle = GlobalReAlloc(*handle,size,GMEM_MOVEABLE);
  3384.  #endif
  3385.  }
  3386.  
  3387.  MEMPTR      env_GraspMem(handle)
  3388.  MEMBLOCK    handle;
  3389.  {
  3390.  #ifdef MAC
  3391.      HLock((Handle)handle);
  3392.      return (MEMPTR) *handle;
  3393.  #else
  3394.      return (MEMPTR) GlobalLock(handle);
  3395.  #endif
  3396.  }
  3397.  
  3398.  VOID        env_UnGraspMem(handle)
  3399.  MEMBLOCK    handle;
  3400.  {
  3401.  #ifdef MAC
  3402.      HUnlock((Handle) handle);
  3403.  #else
  3404.      GlobalUnlock((HANDLE) handle);
  3405.  #endif
  3406.  }
  3407.  
  3408.  VOID        env_FreeMem(handle)
  3409.  MEMBLOCK    handle;
  3410.  {
  3411.  #ifdef MAC
  3412.      DisposHandle((Handle) handle);
  3413.  #else
  3414.      GlobalFree((HANDLE) handle);
  3415.  #endif
  3416.  }
  3417.  
  3418.  MEMSIZE     env_CompactMem(size_wanted)
  3419.  MEMSIZE     size_wanted;
  3420.  {
  3421.  #ifdef MAC
  3422.      return (MEMSIZE) CompactMem(size_wanted);
  3423.  #else
  3424.      return (MEMSIZE) GlobalCompact(size_wanted);
  3425.  #endif
  3426.  }
  3427.  
  3428.  
  3429.  Figure 14:  Nonportable Version of AddGraphicsObjectToModel
  3430.  
  3431.  OBJECTID   AddGraphicsObjectToModel( data_model , object_attributes)
  3432.      HMODEL  data_model;     /* handle to a data_model */
  3433.      ATTR    object_attributes;  /* attributes of new graphics object */
  3434.      {
  3435.          /* id is an index into the data model, represents identifier
  3436.           * for newly created graphics object
  3437.           */
  3438.      OBJECTID    id;
  3439.  
  3440.          /* id of new graphics object, and post-increment
  3441.           * the number of objects in this data_model
  3442.           */
  3443.      id = (**data_model).num_objects++;
  3444.  
  3445.          /* grow the data_model to contain the new graphics object
  3446.           */
  3447.      env_ReAllocMem( (Handle) data_model,
  3448.      (MEMSIZE) (sizeof(int) + ((**data_model).num_objects *
  3449.                                 sizeof(OBJ))));
  3450.  
  3451.          /* call  MakeGraphicsObject()  with a pointer to the
  3452.           * new graphics object
  3453.           */
  3454.      MakeGraphicsObject( &((**data_model).objects[i]),
  3455.                                object_attributes);
  3456.  
  3457.          /* return the id of the newly created graphics object */
  3458.      return id;
  3459.  }
  3460.  
  3461.  
  3462.  Figure 15:  Portable Version of AddGraphicsObjectToModel
  3463.  
  3464.  OBJECTID   AddGraphicsObjectToModel( data_model , object_attributes)
  3465.      HMODEL *    data_model; /* not a handle, but pointer to handle */
  3466.      ATTR        object_attributes;
  3467.      {
  3468.      OBJECTID    id;
  3469.      PMODEL      m;  /* a pointer to data model */
  3470.  
  3471.          /* get pointer to data_model, and lock data_model in memory */
  3472.      m = (PMODEL) env_GraspMem(*data_model);
  3473.  
  3474.          /* get id of graphics object, post-increment num_objects */
  3475.      id = (m->num_objects)++;
  3476.  
  3477.          /* unlock the data_model so that we can resize it */
  3478.      env_UnGraspMem(*data_model);
  3479.  
  3480.          /* resize data_model, passing pointer to handle in case
  3481.           * the value of the handle needs to be changed by ReAlloc
  3482.           */
  3483.      env_ReAllocMem( (MEMBLOCK *) data_model,
  3484.                 (MEMSIZE) (sizeof(int) + ((id + 1) * sizeof(OBJ))));
  3485.  
  3486.          /* lock the data_model again, and get a pointer to it */
  3487.      m = (PMODEL) env_GraspMem(*data_model);
  3488.  
  3489.          /* call the MakeGraphicsObject() routine with pointer to
  3490.           * new graphics object. The argument is of type POBJECT,
  3491.           * rather than OBJECT *, to compensate for segmented
  3492.           * memory referencing on Intel CPUs.
  3493.           */
  3494.      MakeGraphicsObject((POBJECT)&(m->objects[id]), object_attributes);
  3495.  
  3496.          /* unlock data_model */
  3497.      env_UnGraspMem(*data_model);
  3498.  
  3499.          /* return id of newly created graphics object */
  3500.      return id;
  3501.  }
  3502.  
  3503.  
  3504.  Figure 16:  Typedef'd Data Mode
  3505.  
  3506.  typedef struct{
  3507.     int num_objects;    /* number of graphic objects */
  3508.     OBJ objects[1];     /* variable length array */
  3509.     } MODEL;
  3510.  typedef Handle HMODEL; /* a handle to data model */
  3511.  
  3512.  
  3513.  Figure 17:  Using env_AllocMem to Dynamically Allocate a New Data Model
  3514.  
  3515.  HMODEL NewModel
  3516.  {
  3517.     HMODEL  hModel; /* handle to data model */
  3518.     MODEL * pModel; /* pointer to data model */
  3519.     hModel = (HMODEL)  env_AllocMem(sizeof(MODEL));
  3520.     pModel = (MODEL *) env_GraspMem(hModel); /* lock */
  3521.     pModel->num_objects = 0;
  3522.     env_UnGraspMem( hModel ); /* unlock */
  3523.     return hModel;
  3524.  }
  3525.  
  3526.  
  3527.  Figure 18:  Allocating the New Pointer to Model Type
  3528.  
  3529.  HMODEL NewModel
  3530.  {
  3531.     HMODEL  hModel; /* handle to data model */
  3532.     PMODEL  pModel; /* pointer to data model */
  3533.     hModel = (HMODEL)  AllocMem( sizeof(MODEL) );
  3534.     pModel = (PMODEL)  GraspMem( hModel );
  3535.     /* lock mem object */
  3536.     pModel->num_objects = 0;
  3537.     UnGraspMem( hModel ); /* unlock memory object */
  3538.     return hModel;
  3539.  }
  3540.  
  3541.  
  3542.  Developing Applications with Common Source Code for Multiple Environments
  3543.  
  3544.  ───────────────────────────────────────────────────────────────────────────
  3545.  Also see the related article:
  3546.    Development Tools for the Macintosh
  3547.  ───────────────────────────────────────────────────────────────────────────
  3548.  
  3549.  Michael Geary
  3550.  
  3551.  A tale of three systems could easily be told of software written for one
  3552.  system and ported to another. "It was the best of programs, it was the worst
  3553.  of programs..." Writing a program that will run on more than one kind of
  3554.  machine or operating system isn't easy, especially if it's supposed to run
  3555.  on Presentation Manager (hereafter referred to as PM), Windows, and the
  3556.  Macintosh(R). Here we have three vastly different windowing/operating
  3557.  environments running on three different operating systems on two
  3558.  incompatible hardware platforms. Yet the three have much in common.
  3559.  Windows and Presentation Manager share a common user interface, and the
  3560.  Mac(R) user interface is quite similar. This might lead one to expect──or at
  3561.  least hope for──some similarities in the programming environment, and
  3562.  there are some. The three systems do provide many of the same general
  3563.  features, but they are implemented in very different ways.
  3564.  
  3565.  Windows and PM aren't too far apart; even though the detailed coding isn't
  3566.  the same between them, PM's ancestry shows quite clearly. There is a direct
  3567.  correspondence between many of the functions and messages in the two, and
  3568.  overall program architecture is, or can be, virtually identical. There are
  3569.  some major differences, to be sure. Presentation Manager applications can
  3570.  take advantage of all the features of OS/2, including things that have been
  3571.  on many a Windows programmer's wish list: true preemptive multitasking,
  3572.  multiple threads of execution, named pipes. But PM doesn't have every
  3573.  feature of Windows. One glaring omission is the multiline edit control. In
  3574.  Windows you just set a style bit and you get a decent little text
  3575.  editor-the same one used in Notepad, in fact. If you want this in
  3576.  Presentation Manager, you'll have to code it yourself. Still if you stick to
  3577.  their common features, Windows and PM are similar enough to make it
  3578.  relatively easy to port code between them.
  3579.  
  3580.  The Mac Toolbox (that is, the set of function calls available in the Mac's
  3581.  ROM and System files) is a horse of a different color. Besides taking a
  3582.  different approach to many things, the interface is generally built at a
  3583.  lower level than its PC counterparts. While Mac applications are event-
  3584.  driven, they don't really have the message-based architecture that is one
  3585.  of the strengths of Windows and PM. Mac applications have to fuss around
  3586.  with a lot of details to do things that are simple in the other two systems.
  3587.  For example, it's easy to create a standard Mac window complete with a title
  3588.  bar, close box, and zoom box-but if you want the user to actually be able to
  3589.  move, size, or zoom the window, or even to close it, you've got some code to
  3590.  write. When the mouse-down event message comes in, the application must
  3591.  determine where the mouse was pressed (albeit with a simple function call)
  3592.  and then call the proper function to manipulate the window. In Windows and
  3593.  PM this is done for you by DefWindowProc or the frame window and control
  3594.  classes, and the application is notified of the new window status via a
  3595.  message which can be processed or ignored. This makes simple
  3596.  programs simpler, while still giving programmers the opportunity to
  3597.  customize this kind of window handling.
  3598.  
  3599.  This particular difference in architecture complicates the portability issue
  3600.  in more ways than just difficulty of programming. Some kinds of programs
  3601.  are impossible to write on the Macintosh except by resorting to kludges that
  3602.  can't be guaranteed to work in all cases. The toughest are utility
  3603.  programs that manipulate the windows of other applications. Writing my Tiler
  3604.  program was a simple job under Windows and would be just as easy under PM
  3605.  (notwithstanding the fact that Tiler's functionality is already part of
  3606.  the PM shell). All I had to do, after getting the other applications'
  3607.  window handles and determining where the windows should be placed, was to
  3608.  call SetWindowPos for every window. (In PM, just one WinSetMultWindowPos
  3609.  call would do them all with less screen activity.) The interesting thing is
  3610.  that Tiler works fine even with applications like Clock, whose window
  3611.  contents are size dependent. Their windows receive WM_MOVE and WM_SIZE
  3612.  windows, just as if the user moved or sized them, so any position- or size-
  3613.  dependent calculations happen as a matter of course.
  3614.  
  3615.  Several Mac applications do have tiling and other window arrangement
  3616.  options-but only for their own windows. I don't know of any general-purpose
  3617.  tiling desk accessory for the Macintosh. The Mac does have MoveWindow
  3618.  and SizeWindow functions, but if a desk accessory turned them loose on an
  3619.  application's windows, things would not go as planned. There is a big
  3620.  difference between these functions and their equivalents in Windows or PM.
  3621.  On the Mac, there is no WM_MOVE or WM_SIZE message; in fact, there isn't
  3622.  even a window function that these messages can be sent to. In the normal
  3623.  course of events the MoveWindow and SizeWindow functions are called only by
  3624.  the application itself, whether because of mouse clicks or a menu item. The
  3625.  only reason the application knows it must deal with a new window size is
  3626.  because the application itself just called those functions. A Tiler on the
  3627.  Mac would probably move the windows correctly, but it would not do any size-
  3628.  related calculations. For example, scroll bars would remain at their old
  3629.  relative positions instead of being adjusted to fit the new window size.
  3630.  
  3631.  Even MultiFinder(TM) has to resort to various kinds of tricks to get its job
  3632.  done. There is a revealing discussion of these kinds of struggles from an
  3633.  insider's point of view in Phil Goldman's "MultiFinder Revealed," in the
  3634.  August 1988 BYTE.
  3635.  
  3636.  
  3637.  Mac Advantages
  3638.  
  3639.  Just when you think you're involved in too many system details, you
  3640.  discover some of the Mac's greatest strengths. For example, the Mac
  3641.  Toolbox has hooks that let an application substitute its own code for the
  3642.  normal Toolbox code. Windows and PM, of course, have a set of message hook
  3643.  functions, and window functions themselves serve this same purpose. But on
  3644.  the Mac, the intercept functions, called defprocs, are found in many more
  3645.  places. For example, QuickDraw(TM) allows you to replace any of its lowest-
  3646.  level drawing functions with your own, and you can give the Control Manager
  3647.  a defproc to define special controls, like the speaker volume slider in the
  3648.  Control Panel. The Control Manager takes care of mouse tracking and other
  3649.  details, calling back to the defproc to do things like draw the slider and
  3650.  its background. In Windows or PM you would have to implement this kind of
  3651.  control yourself, with much less help from the operating environment.
  3652.  
  3653.  The Mac provides a higher level of support in other areas, too. There are
  3654.  Toolbox calls to put up standard file open and save dialogs, a far cry from
  3655.  the situation in PM and Windows where each application implements its own
  3656.  dialog for these functions. This fact made it possible for Apple to
  3657.  introduce the Hierarchical File System right beside the Mac's old flat file
  3658.  system, without breaking too many existing applications. Imagine the
  3659.  situation if each application had implemented its own file dialogs; there
  3660.  would have been no method built into them to switch directories. Since most
  3661.  applications used the standard file dialogs, an update automatically
  3662.  makes these applications reasonably current. Furthermore, this paves the
  3663.  way for slick little utilities like Findswell. This program adds an extra
  3664.  button to the standard file dialog in most applications; pressing this
  3665.  button lets the directories on a disk be searched for a file name. Just
  3666.  imagine trying to write a utility like this for Windows or PM, where each
  3667.  application is burdened with providing its own file open and save
  3668.  dialogs. No way!
  3669.  
  3670.  As good as the Mac Toolbox is, it just doesn't have either the elegance of
  3671.  a message-based architecture or OS/2 niceties like threads; Mac programmers
  3672.  find themselves getting "down and dirty" more often than PM or Windows
  3673.  programmers, I suspect. This brings out another strength of the Mac: its
  3674.  more open architecture and documentation. Windows and PM prevent dirty
  3675.  programming tricks by not documenting their internal architecture,
  3676.  providing only high-level functions to manipulate things like window
  3677.  structures. This may keep application programmers "well behaved," but it
  3678.  makes life difficult when the facilities provided don't do the job.
  3679.  Furthermore, this philosophy makes it hard to debug competently. The times
  3680.  when my code crashed in Windows, I would have given my eyeteeth to know the
  3681.  details of a window structure or the ever-mysterious Burgermaster, just to
  3682.  give me a fighting chance of figuring out what my code was doing wrong.
  3683.  
  3684.  The Mac and its documentation take an entirely different approach.
  3685.  Inside Macintosh documents nearly all the internal structures used by the
  3686.  Mac Toolbox, which are closely guarded secrets in PM and Windows. There are
  3687.  lots of suggestions concerning what might change in future systems, but at
  3688.  the top of my list is the suggestion that programmers get every bit of
  3689.  information that might be useful. This would bother a traditional
  3690.  operating system designer, because it makes it easy for the application
  3691.  programmer to take control at any level. It's possible to run wild with
  3692.  this and write a program that will break with every new system software
  3693.  release, but on the Mac you can dig into a lot of the internal data
  3694.  structures and still be "well-behaved." This openness has helped make many
  3695.  Mac applications and utilities possible. Windows Version 2.03 did open up
  3696.  the Windows architecture considerably, and Presentation Manager continues
  3697.  that improvement.
  3698.  
  3699.  
  3700.  Portability Issues
  3701.  
  3702.  The deeper an application digs into the internal workings of any system, the
  3703.  harder it is to port. This is true in traditional programming environments
  3704.  as well as the complex windowing systems we're dealing with here. In the
  3705.  worst case, it may be necessary to recode the entire application for each
  3706.  system-a brute force method of porting, but sometimes there is just no
  3707.  choice. One situation that can force complete recoding is the lack of a
  3708.  common programming language. Fortunately several good C compilers are
  3709.  available on the Mac, and C is the language of choice for Windows and PM
  3710.  applications. The Mac Toolbox was designed to be called directly from
  3711.  programs written in Lisa(R) Pascal, so it follows the calling sequence and
  3712.  data formats of Lisa Pascal. Pascal is still very popular on the Mac, but
  3713.  there are too many differences between the Pascal compilers on the different
  3714.  machines to make it a good choice for portability.
  3715.  
  3716.  If C++ were available for developing Windows, PM, and Mac applications, I'd
  3717.  use it instead of C. With inline functions, classes, and inheritance──and
  3718.  all its other object-oriented facilities──C++ is more than an incremental
  3719.  improvement over C for writing portable programs. Given a good library of
  3720.  classes, C++ could hide the underlying environments much more
  3721.  conveniently, and probably more efficiently, than C. But C++ compilers
  3722.  aren't yet available for every system we are considering, so C it is for
  3723.  now.
  3724.  
  3725.  Even though C doesn't help much with the differences in Application
  3726.  Programming Interfaces (APIs) and application structure, it does a
  3727.  reasonable job of letting you write code that will run on different
  3728.  systems. There are still a few pitfalls, and the Pascal orientation of the
  3729.  Mac Toolbox (and to a lesser degree of OS/2 and Windows) introduces
  3730.  additional problems. The "traditional" portability issues have been well
  3731.  covered in the literature, so I'll just touch on a few.
  3732.  
  3733.  Byte and word ordering. The Motorola(R) 68000 and the Intel(R) x86 series
  3734.  processors order their data in opposite directions. The 68000 is a "big
  3735.  endian" machine, that is, the high-order byte or word comes before the low-
  3736.  order one. The x86 machines are "little endian," with just the opposite data
  3737.  ordering. This affects any binary files meant to be portable between
  3738.  machines, along with data structure definitions and data access macros such
  3739.  as HIWORD and LOWORD in Windows.
  3740.  
  3741.  Number lengths. Is an "int" a 16-bit or 32-bit value? This problem is
  3742.  easily avoided by never using the native C data types and instead typedefing
  3743.  everything and using the new types only. A good convention is to typedef all
  3744.  the C types with the same name in uppercase, for example, typedef short
  3745.  SHORT. Then, for all code that interfaces with the underlying system, use
  3746.  SHORT or LONG instead of INT, where SHORT is 16 bits and LONG is 32 bits.
  3747.  
  3748.  ANSI C vs. K&R C. In the beginning everyone used Kernighan and Ritchie, but
  3749.  now ANSI is coming into widespread use. Microsoft(R) C, used for most
  3750.  Windows and PM applications, is nearly a full ANSI standard compiler
  3751.  (granted that the ANSI standard is still in draft form). None of the Mac C
  3752.  compilers are quite up to speed in this area. MPW(TM) C and Lightspeed(R) C
  3753.  do include some of the more useful ANSI features such as prototypes and
  3754.  the wonderful vsprintf function, but the implementations aren't as
  3755.  complete. MPW C even has the chutzpah to accept prototypes syntactically
  3756.  but fail to do any of the type checking implied by them. Hopefully updates
  3757.  will remedy these kinds of deficiencies, but meanwhile it helps to use the
  3758.  newer ANSI features sparingly.
  3759.  
  3760.  C vs. Pascal calling sequences. Since the Mac Toolbox was written to be
  3761.  called from Pascal, the popular Mac C compilers offer a choice between
  3762.  Pascal and C calling sequences. Microsoft C also provides both kinds of
  3763.  calling sequences, but its Pascal calling sequence is somewhat different
  3764.  from the Mac's. Both provide for the main difference, parameters being
  3765.  pushed left-to-right instead of right-to-left as in the C calling sequence,
  3766.  and the pascal keyword is used in each case to switch to the Pascal calling
  3767.  sequence. However, the pascal keyword has to be used in different places.
  3768.  In Lightspeed C and MPW C, you would declare a pascal-sequence function
  3769.  returning a long like this:
  3770.  
  3771.    pascal long foo( void );
  3772.  
  3773.  Microsoft C, on the other hand, requires this:
  3774.  
  3775.    long pascal foo( void );
  3776.  
  3777.  This difference makes the pascal keyword rather useless for porting, unless
  3778.  you do something really clunky like this:
  3779.  
  3780.    #ifdef MICROSOFT_C
  3781.    #define P1
  3782.    #define P2 pascal
  3783.    #else  /* Macintosh */
  3784.    #define P1 pascal
  3785.    #define P2
  3786.    #endif
  3787.    P1 long P2 foo( void );
  3788.  
  3789.  Rather than going to these lengths, it may be best to avoid the Pascal
  3790.  calling sequence except for code that directly interfaces to the Mac
  3791.  Toolbox or to the PM or Windows API functions. This should only cause
  3792.  problems in code that assumes one calling sequence or the other. PMWIN.H,
  3793.  the PM's header file, does have a number of structure definitions and macros
  3794.  that assume the Pascal calling sequence.
  3795.  
  3796.  C strings vs. Pascal strings. Nearly all the Mac Toolbox functions expect
  3797.  strings to be in the Lisa Pascal format: a length byte followed by that many
  3798.  bytes of text. (Pascal strings are limited to 255 bytes.) Contrariwise,
  3799.  Windows, OS/2, and the C compilers on both machines use strings in the
  3800.  zero-terminated C format (ASCIIZ). The Mac C compilers provide various ways
  3801.  of dealing with this, both with conversion functions and, in some compilers,
  3802.  with a 'p' prefix for string constants; "pString" would be the same thing
  3803.  as "String", but in Pascal format. Strings loaded from resources are the
  3804.  native type for each environment: Pascal on the Mac, C on Windows and PM.
  3805.  
  3806.  
  3807.  User Interface Portability
  3808.  
  3809.  When porting code between different windowing systems, the traditional
  3810.  portability problems are really the least of our worries. Since programs on
  3811.  these systems are so API-intensive and the APIs are so different, the
  3812.  code that talks directly to the underlying environment must change for each
  3813.  system (except for the possibility mentioned above, of using macros to make
  3814.  PM and Windows look more alike). And the Mac's user interface, though
  3815.  similar to Windows and PM, is not the same, though you can get them them to
  3816.  be as similar as you want, depending on how much code you want to write.
  3817.  After all, there's a bitmap graphics engine underneath each of these
  3818.  systems, and you can draw whatever you want on the screen and interact
  3819.  with the user however you choose, regardless of whether it fits the user
  3820.  interface conventions of the system that you're on.
  3821.  
  3822.  The only reason I mention this is that people have tried it. Don't. On the
  3823.  Mac, your application should look and feel like a Mac application; on
  3824.  Windows and PM, it should look and feel like a Windows/PM application. Then,
  3825.  within those constraints, your Mac and Windows/PM versions should be as
  3826.  similar as possible. Trust me on this. Or if you don't trust me, read the
  3827.  reviews of Mac products that are "un-Mac-like," or of Windows/PM packages
  3828.  that have their own ideas about user interaction. That's not to say that
  3829.  logical extensions to either user interface aren't worthwhile, but
  3830.  remember that Mac users will also be using other Mac software, just as
  3831.  Windows and PM users will use other software for those systems. (Of course,
  3832.  this doesn't apply to true standalone turnkey applications, where the
  3833.  computer is dedicated to running specific software.)
  3834.  
  3835.  
  3836.  Child Windows
  3837.  
  3838.  What differences in these user interfaces do we have to consider? Most
  3839.  significant are child windows, the appearance and features of a standard
  3840.  document window, and the use of menus.
  3841.  
  3842.  Windows and PM have child windows that can be nested to any level, overlap,
  3843.  and be manipulated just like top-level windows. Many applications use these
  3844.  invisibly as a programming convenience, but the Multiple Document Interface
  3845.  (MDI) uses child windows visibly and extensively.
  3846.  
  3847.  The Mac has no child windows. It is possible to build them, but this
  3848.  involves a lot of reinventing the wheel. Fortunately the Mac's user
  3849.  interface convention corresponding to MDI doesn't involve child windows. In
  3850.  MDI, there is an application workspace window that contains the
  3851.  application's menu bar, and document windows are created as child windows in
  3852.  this window. Mac applications move everything up one level, as it were; they
  3853.  take over the whole screen, with the application menu bar at the top, and
  3854.  document windows are top-level Macintosh windows. There is a
  3855.  straightforward path for porting multiple-document applications between the
  3856.  Mac, Windows, and PM; use MDI on the latter two and the standard Mac
  3857.  conventions on the Mac.
  3858.  
  3859.  The Mac does have controls corresponding to all the control window classes
  3860.  in Windows, although not to the new frame controls added in PM. These are
  3861.  not implemented as child windows, but through a separate control manager
  3862.  and other pieces of code. Although they are more difficult to program, these
  3863.  controls are generally more flexible and powerful than the child window
  3864.  controls in Windows or PM. In particular, the text editor and list manager
  3865.  go far beyond their counterparts in PM and Windows.
  3866.  
  3867.  
  3868.  Menus
  3869.  
  3870.  Menus are quite different among the three systems. Although Windows and
  3871.  Presentation Manager nominally have the same user interface, some things
  3872.  like menus really aren't the same; they are just used in the same way by
  3873.  convention.
  3874.  
  3875.  Under Windows, top-level windows can have menu bars, but child windows
  3876.  cannot, since the child window ID (dialog ID) and the menu handle are
  3877.  overloaded into the same field in Windows' internal window structure. Each
  3878.  item in the menu bar can be an actual menu item or have a pull-down menu
  3879.  associated with it, and top-level windows and child windows can each have
  3880.  Control (System) menu icons, with the associated pull-down menus. Menu items
  3881.  in a menu bar or a pull-down menu can be either text or bitmaps.
  3882.  
  3883.  A Presentation Manager menu is no more than a control window class. As with
  3884.  any other window class, you can create a menu window at any level of the
  3885.  window hierarchy. Menu bars and pull-down menus are just two variations on
  3886.  the same menu window class. The standard frame window in PM creates a number
  3887.  of different menu windows to implement the standard PM/Windows interface;
  3888.  the menu bar is a menu window, the Control menu icon is another menu window,
  3889.  and the Minimize/Maximize icons make up a third menu window. (It seemed
  3890.  strange to me at first to think of those icons as menus, but after all a
  3891.  menu can include bitmaps, and these icons are just like the system menu
  3892.  icon, but without pull-down menus.) The various pull-down menus are
  3893.  themselves menu windows, invisible (and in fact children of the
  3894.  HWND_DESKTOP window) until needed for display. But PM menus are not limited
  3895.  to this standard usage-except in the user interface guidelines.
  3896.  
  3897.  The Macintosh originally had one menu bar at the top of the screen, with a
  3898.  pull-down menu for each menu item. By convention, the first item of every
  3899.  application is a tiny Apple(R) logo, whose pull-down menu is analogous in
  3900.  spirit if not in function to the Control menu of a Windows or PM
  3901.  application. Menu bar items are text only, in the standard system font (the
  3902.  Apple logo is simply a character in the Chicago font). Pull-down menus are
  3903.  normally text, but an application can supply a menu defproc to vary a pull-
  3904.  down menu's standard appearance.
  3905.  
  3906.  Mac applications generally adhere to this basic menu structure, but over
  3907.  the last few years the Macintosh menu manager has been enhanced in several
  3908.  ways. Pop-up menus can appear anywhere on the screen, and with a little work
  3909.  these can simulate PM menus. Mac applications have begun to use these,
  3910.  especially for multiple-choice items in dialog boxes. Hierarchical menus
  3911.  let pull-down menu items easily call forth an additional complete pull-down
  3912.  menu. Menus too long to fit on the screen now automatically scroll as needed
  3913.  during menu selection. Some new applications, notably HyperCard(R), have
  3914.  implemented tear-off menus. Especially convenient for tool or pattern
  3915.  palettes, these are pull-down menus that can easily be dragged to other
  3916.  screen locations, where they remain visible as separate palette windows.
  3917.  
  3918.  These features are great, but portability in this case leads to a lot of
  3919.  work or to the lowest common denominator-something like Windows crossed
  3920.  with the original Mac menu structure. Just as multiple-document
  3921.  applications can use either MDI or the Mac's menu bar, a single-document
  3922.  application in Windows can take its main window's menu bar and make it the
  3923.  menu bar on the Mac. Windows applications having menus in more than one
  3924.  window require some reworking. Also, applications can't count on scrolling
  3925.  menus; any menu that might be too long, such as a Font menu, must be checked
  3926.  against the screen height. If it's not going to fit, a list box or something
  3927.  else should be used.
  3928.  
  3929.  Aside from the menu appearance and location, the actual menu items used
  3930.  can be very similar between the Mac and both Windows and PM. An application
  3931.  should have a File menu and an Edit menu, followed by additional
  3932.  application-specific menus. The standard File and Edit menus are nearly
  3933.  identical across systems; the main exception is the different use of
  3934.  Command-key and Alt-key shortcuts, which is easily resolved by maintaining
  3935.  separate resource files for each system. And in this case that's as easy
  3936.  as trying to convert resource files back and forth.
  3937.  
  3938.  There are some differences in standard menus. For example, on the Mac, the
  3939.  About... box is at the top of the Apple menu, followed by the desk
  3940.  accessories and the MultiFinder menu. In most Windows and PM applications,
  3941.  this migrates to the bottom of the File menu, right below the Exit item that
  3942.  replaces the Mac's Quit item. This Exit item violates the IBM(R) Common
  3943.  User Access (CUA) standard, which calls for an Exit menu in the menu bar,
  3944.  with Exit and Resume items in its pull-down menu, and frankly, this is one
  3945.  violation that is necessary.
  3946.  
  3947.  
  3948.  Document Windows and Dialog Boxes
  3949.  
  3950.  Windows and PM have a common style for standard document windows and
  3951.  dialog boxes, although in each case there are other options. Modal dialog
  3952.  boxes on the Mac are virtually identical to their Windows and PM
  3953.  counterparts, but standard document windows are somewhat different. The
  3954.  title bar is the same, except a close box replaces the Control menu icon,
  3955.  providing only its Close function, and a zoom box replaces only the Maximize
  3956.  icon. There is nothing to replace the Minimize icon, since Mac application
  3957.  windows do not normally have an iconic state. There is no thick frame for
  3958.  window sizing──just the size box in the lower right corner. Scroll bars, if
  3959.  present, work identically. Interestingly, none of these differences really
  3960.  affect portability; they affect visual appearance and the exact mouse (or
  3961.  keyboard) interface, but provide essentially the same functionality
  3962.  except for the lack of an iconic state.
  3963.  
  3964.  As to the contents of a document window or dialog box, dialog boxes are
  3965.  similar across all three systems. Although recent Mac dialog boxes have
  3966.  started to use pop-up menus and other new interface features, the typical
  3967.  Windows or PM dialog box would look at home on the Mac and vice versa. This
  3968.  includes both modal and modeless dialog boxes. (There is no distinction
  3969.  between system modal and application or window modal on the Mac; under
  3970.  MultiFinder, modal dialog boxes are all system modal.)
  3971.  
  3972.  One feature of Windows and PM dialog boxes not often seen on the Mac is the
  3973.  Alt-letter shortcuts for navigating around the box. This is one case where
  3974.  CUA improved on the original Windows and Mac user interfaces; in a large
  3975.  dialog box the Alt-letters can be very convenient. They are worth
  3976.  implementing on the Mac, although there isn't any built-in support for
  3977.  them.
  3978.  
  3979.  The content of document windows is for the most part application
  3980.  dependent. Fortunately, the same kinds of user interface techniques are
  3981.  appropriate on all three systems. Users expect to find virtually the same
  3982.  keyboard and mouse interface on the Mac as in Windows and PM. Applications
  3983.  for the latter systems usually have a more substantial keyboard interface
  3984.  since they must work without a mouse.
  3985.  
  3986.  
  3987.  Portability Techniques
  3988.  
  3989.  "Vanilla" applications with relatively standard document windows, dialog
  3990.  boxes, and menus are the easiest to port. Specialized programs like Tiler,
  3991.  Spellswell(TM)──or most Mac desk accessories for that matter-are tougher.
  3992.  But in the context of using windows and menus in fairly standard ways,
  3993.  there's room to build a competitive, high quality application──vanilla with
  3994.  all the toppings. Packages like PageMaker(R) show that it's possible to move
  3995.  lots of code among these systems with a high degree of user interface
  3996.  refinement and compatibility. It's just a small matter of programming...
  3997.  
  3998.  But wait, we're supposed to design before we code, right? We had better
  3999.  figure out what approach to use. Coding in C gives us a reasonable degree
  4000.  of language portability, but what's the best way to deal with the
  4001.  different APIs involved? There isn't really one best way; it depends on the
  4002.  situation.
  4003.  
  4004.  Besides maintaining separate source code for each system, another kind of
  4005.  brute force technique is to keep common source code but use a zillion
  4006.  #ifdef's to isolate machine specific code. This is usually a bad bet; it
  4007.  ends up harder to read and maintain than separate source.
  4008.  
  4009.  Macros are a fine tool for isolating system dependencies. It's a pity C's
  4010.  macro processor is so limited, because it gives just a taste of the kinds of
  4011.  things that can be done with macros. Even with its limitations, the C
  4012.  preprocessor lets you define simple text-substitution macros with
  4013.  parameters, and that alone is plenty. The Presentation Manager Conversion
  4014.  Guide includes a comprehensive example of using macros to port the Cardfile
  4015.  program from Windows to PM. C macros don't do the whole job, so the Cardfile
  4016.  example also has a fair number of #ifdef's and machine-specific helper
  4017.  functions. The macros do reduce the number of #ifdef's enough that they
  4018.  don't hurt the code's readability very much. When porting between Windows
  4019.  and Presentation Manager, this macro approach works very well, because of
  4020.  the inherent similarity of the two systems. It's also very efficient.
  4021.  
  4022.  When the Mac comes into the picture, the macro scheme starts to fall apart.
  4023.  Because of the radically different architectures, it takes a considerable
  4024.  body of machine-specific code no matter how you approach the problem. The
  4025.  question becomes how best to divide the application between system-
  4026.  independent code and system-specific code, and which services the system-
  4027.  specific code provides to the rest. One way to look at this is to view the
  4028.  independent code as running in a virtual operating environment that is the
  4029.  same on all systems, and the specific code as implementing that environment
  4030.  for each system.
  4031.  
  4032.  The next question is just what that virtual operating environment is. It
  4033.  can provide its own unique set of APIs, which are implemented by the system-
  4034.  specific code on each system; or it can use the APIs of one of the systems
  4035.  directly, in which case code on the other systems mimics those APIs. For
  4036.  example, if you were starting with a Mac application and wanted to port it
  4037.  to PM and Windows, it would be very useful to have the same Mac Toolbox
  4038.  calls available on all three systems. Barring the existence of some
  4039.  commercial library providing these functions, there's a lot of coding
  4040.  involved in this, to say the least. It is true that you wouldn't have to
  4041.  implement every feature of the base system, only those particular features
  4042.  that your application actually uses.
  4043.  
  4044.  Making up new APIs for coding the application is also a valid approach, as
  4045.  Aldus did in writing a version of PageMaker that runs on the Mac and
  4046.  Windows (and shortly for Presentation Manager). As described in "Aldus:
  4047.  Preparing PageMaker for the Move to Windows" MSJ, (Vol. 1, No. 2), Aldus
  4048.  split the project into "core code" and "edge code." The core code is the
  4049.  same on both systems, and the edge code interfaces to the target systems,
  4050.  providing a common set of functions that the core code uses.
  4051.  
  4052.  There is also a commercial product that uses this method to provide
  4053.  portability between the Mac and Windows. The Extensible Virtual Toolkit
  4054.  (XVT) from Advanced Programming Institute, Ltd. provides a common interface
  4055.  for these two systems and is being extended to support Presentation Manager
  4056.  and X Windows as well. XVT provides a layer on top of each of the target
  4057.  environments, with its own API, its own program structure, and its own user
  4058.  interface. XVT uses a "vanilla with all the toppings" user interface like
  4059.  the one I've been describing. Windows versions use the Multiple Document
  4060.  Interface or a single document window, and Macintosh versions use the
  4061.  standard Mac multiple window interface. Dialog boxes and menus work as
  4062.  expected on both systems.
  4063.  
  4064.  XVT does not provide as comprehensive or flexible an environment as any of
  4065.  the native environments, but it provides enough to write a large variety of
  4066.  applications. It doesn't support some features, such as modeless dialogs,
  4067.  but there are plans for it to do so in future releases. It's possible to
  4068.  implement features not supported by XVT by directly calling the native
  4069.  environment, but this is nonportable. Nonetheless, it's possible to write
  4070.  fairly sophisticated programs using XVT alone; XVT-Draw, a drawing program
  4071.  written purely in XVT code, is a full-featured drawing program that shows
  4072.  the power available. I'm looking forward to seeing the Presentation
  4073.  Manager version of XVT.
  4074.  
  4075.  One benefit of XVT's approach is that it's possible to invent an API that is
  4076.  simpler and easier to program than any of the target systems. Although XVT
  4077.  was written to facilitate portability, many people have used it as a
  4078.  simpler way to program just one of the target systems. It also provides very
  4079.  high-level support for a few things commonly used in applications. For
  4080.  example, standard Font and Style menus can be generated simply by setting a
  4081.  flag, and there is a complete Help system that can be built into XVT
  4082.  applications.
  4083.  
  4084.  As an experiment I took the other approach to portability, picking one
  4085.  environment as the base and writing native code for it, then implementing
  4086.  portions of that environment on the other systems. Since Presentation
  4087.  Manager is my favorite of the three in terms of its APIs presenting the
  4088.  greatest amount of functionality, I used that as the base system. I also
  4089.  wanted a taste of what is involved in implementing an SAA-compatible
  4090.  programming environment. Although there are now very few applications
  4091.  written to SAA standards, many are coming, and it would be a plus to be able
  4092.  to run them on hardware other than IBM's.
  4093.  
  4094.  I also expect PM to become the single largest end-user and business
  4095.  environment, so it makes good sense to focus the primary attention there,
  4096.  and to port my application to Windows and the Mac as secondary markets. Of
  4097.  course this also suggests, and I would certainly like to see, that all
  4098.  those excellent Macintosh applications be ported to Presentation Manager.
  4099.  (See the accompanying article which discusses porting Macintosh
  4100.  applications to Windows and PM.──Ed.)
  4101.  
  4102.  Taking one system as the base environment simplifies the problem in two
  4103.  areas. It eliminates the need to invent common APIs, and it eases
  4104.  debugging of at least one version of the application, because it runs as
  4105.  native code on the base system. It also means that useful features of the
  4106.  base system become available on the others. In the case of PM as the base,
  4107.  this means, for example, that child windows──and window functions in
  4108.  general──become available on the Macintosh. When I was first programming
  4109.  the Mac a few years ago, I had never heard of child windows so I didn't
  4110.  miss them. But now that I've had a chance to use them in my Windows and PM
  4111.  programming, I don't know how I got along without them.
  4112.  
  4113.  
  4114.  Sleuth
  4115.  
  4116.  As a test case for this portability method, I used a souped-up version of
  4117.  a program I wrote for Windows some time ago, which I now call Sleuth. It is
  4118.  written as a native OS/2 application using Presentation Manager. I wrote
  4119.  two libraries; WinPM for Windows, and MacPM for the Macintosh that provide
  4120.  just enough Presentation Manager functionality to let Sleuth run on those
  4121.  systems with no change to its source code.
  4122.  
  4123.  Sleuth (see Figure 1 for PM source code) creates a main window that is a
  4124.  standard document window-movable, resizable, and scrollable horizontally
  4125.  and vertically. In this window, Sleuth lists all existing windows, visible
  4126.  and invisible, in an outline showing the hierarchical relationship of
  4127.  parent and child windows. Each entry in the list shows the basic information
  4128.  about that window: its handle, ID, class name, rectangle, and text. You can
  4129.  double-click any entry to show all the information Sleuth can find out about
  4130.  a particular window, and see the information in a new window that Sleuth
  4131.  opens for each entry you double-click. You can also use the cursor keys to
  4132.  select an entry instead and then hit Enter or select the "Show Detail" menu
  4133.  item.
  4134.  
  4135.  Figures 2-5 show Sleuth running on OS/2 and the Macintosh; note that the
  4136.  OS/2 version shows information about more windows than the others. That's
  4137.  because the native version of Sleuth captures information about every
  4138.  window created by Presentation Manager, regardless of the application
  4139.  that created them. The other versions only know about the windows created by
  4140.  the Presentation Manager emulator, either MacPM or WinPM, which
  4141.  essentially means only the windows created by Sleuth itself. While Sleuth
  4142.  on its native environment is useful for looking at how other applications
  4143.  do things, it isn't on the other systems. However, if I were implementing
  4144.  a "serious" application on MacPM and WinPM, the Sleuth window is the first
  4145.  thing I would use in debugging the rest of the program. (I would also add a
  4146.  message trace window as in Microsoft's Spy program; that isn't in this
  4147.  version of Sleuth.)
  4148.  
  4149.  Even as simple as it is, Sleuth uses a lot of Presentation Manager's
  4150.  functionality. Many of its features──menus, text painting, scrolling──are
  4151.  the same things that would be used by, say, a simple text editor. In fact,
  4152.  an interesting thing happened as I was implementing MacPM (Sleuth's first
  4153.  excursion into foreign territory). I had to sweat out a lot of code to get
  4154.  a small portion of Sleuth running: the event/message manager, window
  4155.  manager, and all sorts of utility functions such as mapping between PM and
  4156.  Mac coordinates. Big chunks of the PM version of Sleuth were #ifdef'd out
  4157.  for a long time while I just tried to get one message through a window
  4158.  function correctly. Once I got past a certain point, things started coming
  4159.  together much more smoothly. With the overall framework in place, filling
  4160.  in the bits and pieces became much easier.
  4161.  
  4162.  
  4163.  MacPM
  4164.  
  4165.  MacPM provides enough of the PM functions and messages to let Sleuth, as
  4166.  written for PM, run on the Macintosh. All that is required is recompiling
  4167.  the code on the Mac, using MacPM's .H files, and linking it with the MacPM
  4168.  code. Only MacPM makes any native Macintosh calls; Sleuth doesn't even know
  4169.  it is running on a Macintosh.
  4170.  
  4171.  MacPM makes no attempt to completely emulate Presentation Manager. It
  4172.  provides only those portions of PM that Sleuth actually uses. In many cases,
  4173.  I did try to code MacPM functions and messages the "right" way, even though
  4174.  Sleuth only uses some of their options, but I did take shortcuts. The prime
  4175.  goal was to get a particular application running. With different
  4176.  applications, there are different areas you have to concentrate on, but
  4177.  the central features──PM-style message and window management──are similar.
  4178.  You can build any degree of compatibility you wish into a library like
  4179.  MacPM, up to virtually 100 percent.
  4180.  
  4181.  The best way to understand MacPM is to follow how it is used by Sleuth. If
  4182.  you have the MacPM code on your Macintosh, you can trace through it using
  4183.  Lightspeed C's source debugger while you read this description. (Full source
  4184.  code for Sleuth and MacPM is available from any of the bulletin boards
  4185.  listed on the back page of this issue.──Ed.)
  4186.  
  4187.  
  4188.  Initialization
  4189.  
  4190.  As with all good PM programs, Sleuth starts out with calls to WinInitialize
  4191.  and WinCreateMsgQueue. These functions are in MpmInit.c. In PM,
  4192.  WinInitialize just initializes the heap- and atom-management functions,
  4193.  leaving the graphics and windowing initialization for WinCreateMsgQueue.
  4194.  MacPM follows the same pattern. WinInitialize (see Figure 6) does some
  4195.  Macintosh memory management initialization and returns a dummy HAB
  4196.  (anchor block handle) value. (I've cheated already. Not only have I skipped
  4197.  the heap and atom initialization──since MacPM doesn't yet support those
  4198.  functions──but by just pretending to have an anchor, I'm risking getting
  4199.  lost at sea. Since MacPM is linked in as part of a single application, it
  4200.  can get away with not really allocating anchor blocks and a few other
  4201.  things.)
  4202.  
  4203.  WinInitialize also opens up a resource file called MacPM.rsrc. Macintosh
  4204.  resources are used similarly to PM and Windows resources, but there is more
  4205.  flexibility about where resources are located. Some resources are found in
  4206.  the System file or in ROM, others in the application executable file, and
  4207.  still others are in resource files explicitly opened with OpenResFile. Under
  4208.  Lightspeed C, it's a common practice to have a separate resource file
  4209.  during development. This helps provide a quick turnaround; there is no
  4210.  need to merge the resources into the application file. When the
  4211.  application is ready to ship, a standalone application file can be built
  4212.  and the OpenResFile call removed. (Or the call can even be left in; it will
  4213.  try to open the file but fail, and we can just ignore the error return
  4214.  code.)
  4215.  
  4216.  WinCreateMsgQueue (see Figure 7) is where the real work begins. First it
  4217.  does the rest of the generic Macintosh initialization that you will find
  4218.  at the beginning of any Mac application-initializing QuickDraw, the Event
  4219.  Manager, the Window Manager, and assorted friends. Then it calls
  4220.  MpmInitSysValues to initialize the system values that WinQuerySysValue can
  4221.  access. These values are derived from appropriate Macintosh functions or
  4222.  values-hard-coded where the corresponding Mac value is an absolute
  4223.  number of pixels. For example, the width of a standard Mac vertical scroll
  4224.  bar is 16 pixels, now and forever. This is hard-coded into nearly every Mac
  4225.  application.
  4226.  
  4227.  Next, WinCreateMsgQueue registers all of the predefined window classes,
  4228.  making a call to WinRegisterClass for each one. This function is pretty
  4229.  straightforward. First it checks if the class is already defined, and if
  4230.  not it allocates a block of memory and stores the class information in it.
  4231.  This block includes a CLASSINFO structure as defined in PM, along with a
  4232.  few other pieces of information about the class. The class blocks are kept
  4233.  in a linked list so WinCreateWindow can search through them. In theory,
  4234.  MacPM should have the PM atom-management functions to search for a class
  4235.  name, but I took a shortcut using a "quick-and-dirty" hashing function.
  4236.  
  4237.  WinCreateMsgQueue wraps up its work by creating two predefined windows:
  4238.  the desktop and object windows. Presentation Manager apparently creates
  4239.  these windows as special cases; they don't have window classes like other
  4240.  windows. In MacPM, it seemed simpler to make up an additional window class,
  4241.  WC_DESKTOP, and then call WinCreateWindow to create these two windows as it
  4242.  does the others. They are still special cases in that they have no
  4243.  corresponding Mac windows. The desktop window lets you write to the
  4244.  entire screen, just as in PM. Object windows aren't really implemented in
  4245.  MacPM; it creates the top level object window but doesn't do much else with
  4246.  it.
  4247.  
  4248.  
  4249.  Creating a Window
  4250.  
  4251.  With WinCreateWindow (see Figure 8), found in MpmCreate.c, we are getting
  4252.  into the real inner workings of MacPM. As I mentioned earlier, the
  4253.  Macintosh has nothing corresponding to PM's hierarchy of windows. There
  4254.  is only a single window level, corresponding to a main window in PM. There
  4255.  are no child windows nor a desktop window. Nor does the Mac have the concept
  4256.  of a window function to which messages can be sent. Macintosh events are a
  4257.  rough subset of PM messages, and the Mac provides functions to find out
  4258.  which window a message relates to, but the interface is generally at a
  4259.  lower level. For example, there is no WinDefWindowProc to handle default
  4260.  conditions; if you don't include code to check for, say, a mouse click in
  4261.  the close box, then the user's going to have a tough time closing your
  4262.  window. Contrast this with the WM_SYSCOMMAND/SC_CLOSE message in PM or
  4263.  Windows, which you can handle specially if you want or just disregard and
  4264.  pass through to WinDefWindowProc.
  4265.  
  4266.  Since all these facilities are so fundamental to PM coding, WinCreateWindow
  4267.  creates a window structure that can hold the information necessary for a PM
  4268.  window-desktop, main (top-level), or child. There is a window function
  4269.  pointer, taken from the window class, along with all the WinCreateWindow
  4270.  information; style flags, owner and parent window handles (along with
  4271.  next/previous sibling and first/last child window handles), PM-style window
  4272.  size and location, etc.
  4273.  
  4274.  To communicate with the Macintosh world, the structure also holds a pointer
  4275.  (WindowPeek) to the corresponding Macintosh window structure. All child
  4276.  windows of a given main window have the same window pointer. For child
  4277.  windows that implement button and scroll bar controls, there is also a
  4278.  handle (ControlHandle) to the actual Macintosh control.
  4279.  
  4280.  There is also a Mac adjustment rectangle, rectAdj, to help translate PM
  4281.  window coordinates into the proper Macintosh coordinate space. The PM
  4282.  child windows (and main window) are all defined the same way they are in the
  4283.  real PM. Coordinates are always relative to the parent window, and the Y
  4284.  coordinate counts up from the bottom──unlike the Mac and Windows, where Y
  4285.  counts from the top down. Most Mac Toolbox functions expect coordinates to
  4286.  be relative to the Mac's window client rectangle. The rectAdj field contains
  4287.  the displacement (for all four sides) to get from the Mac window rectangle
  4288.  to the Mac client rectangle. With this adjustment, it's possible to convert
  4289.  any PM coordinate to the corresponding Mac coordinate, and vice versa. The
  4290.  file MpmMap.c contains several mapping functions that convert points or
  4291.  rectangles back and forth between Mac and PM coordinates. As you might
  4292.  guess, these functions are used extensively throughout MacPM.
  4293.  
  4294.  One last field in the MacPM window structure is the "window kind." There
  4295.  are four different kinds of windows as far as MacPM is concerned, so
  4296.  WinCreateWindow saves one of the following values in the window structure
  4297.  and then takes care of some special processing for each:
  4298.  
  4299.    ■  WK_OBJECT (object window): No special processing; not really
  4300.       supported in this version of MacPM.
  4301.  
  4302.    ■  WK_DESKTOP (desktop window): Save the screen boundaries and a pointer
  4303.       to the Mac's full-screen graphics port (WMgrPort).
  4304.  
  4305.    ■  WK_MAIN (top-level Macintosh window): Create a Macintosh window. The
  4306.       Mac provides several styles of windows, so we choose the best style
  4307.       based on the PM style flags and then call the Mac's NewWindow function
  4308.       to create the actual window. Not all PM styles are supported; for
  4309.       example, a borderless window will get a border anyway. It's possible to
  4310.       create a borderless Mac window, but it takes more code (in Mac terms, a
  4311.       window defproc), and Sleuth doesn't need one anyway. NewWindow returns
  4312.       a WindowPeek pointer to the Macintosh window structure; save that
  4313.       pointer in our window structure.
  4314.  
  4315.    ■  WK_CHILD (child window): Set up the appropriate "relative"
  4316.       fields──such as hwndNextSibling, hwndTopChild, etc.──for this window
  4317.       and its parent and siblings. This wasn't necessary for WK_MAIN windows,
  4318.       since we relied on the Macintosh's window list for main windows. But
  4319.       for child windows we have to do all this bookkeeping.
  4320.  
  4321.  At this point, the window structure is completely initialized, and
  4322.  WinCreateWindow sends a WM_CREATE message to the window. Note that the
  4323.  window is invisible at first, regardless of the WS_VISIBLE flag. Next,
  4324.  WinSetWindowPos is called in order to set the window position and send the
  4325.  WM_ADJUSTWINDOWRECT, WM_MOVE, and WM_SIZE messages. WinSetWindowPos and
  4326.  WinSetMultWindowPos are interesting functions we'll come back to a little
  4327.  later.
  4328.  
  4329.  One final detail and then WinCreateWindow's job is done. Since the window
  4330.  was created invisible, we now check the WS_VISIBLE flag and call
  4331.  WinShowWindow if necessary. This makes the window visible and sends a
  4332.  WM_SHOW message to the window function.
  4333.  
  4334.  
  4335.  Standard Windows
  4336.  
  4337.  After WinCreateMsgQueue returns, Sleuth registers its own client window
  4338.  class and then creates its main window with WinCreateStdWindow (see
  4339.  Figure 9). This function, found in MpmFrame.c, is responsible for creating a
  4340.  document window as well as the standard frame control windows and client
  4341.  window. It sounds like it needs to do a bunch of WinCreateWindow calls, and
  4342.  that's basically what it does. WinCreateStdWindow assigns a default window
  4343.  size and position for cases where WS_VISIBLE is specified. (If WS_VISIBLE
  4344.  isn't set, it creates the window with size and position values zeroed out,
  4345.  under the assumption that the application will position the window before
  4346.  making it visible.) Then a WinCreateWindow call creates the frame window
  4347.  itself.
  4348.  
  4349.  Now it's time to create the frame control windows according to the FCF_
  4350.  flags passed to WinCreateStdWindow. Here we call an internal function,
  4351.  MpmFrameUpdate, in order to create the proper windows. MpmFrameUpdate is
  4352.  also called if the frame window gets a WM_UPDATEFRAME message. (However,
  4353.  the early PM documentation I worked with wasn't very clear about how this
  4354.  should operate, so it's unlikely that I implemented it correctly.)
  4355.  
  4356.  When all of this is finished, WinCreateStdWindow creates the client window,
  4357.  and then-if WS_VISIBLE is specified-sends a WM_FORMATFRAME message to the
  4358.  frame window and calls WinShowWindow in order to make it all visible.
  4359.  WM_FORMATFRAME tells the frame window function in MacPM to position all its
  4360.  child windows according to the current frame window size.
  4361.  
  4362.  
  4363.  Frame Window Details
  4364.  
  4365.  Some of the frame window initialization is handled inside
  4366.  WinCreateStdWindow, but as much as possible is parceled out to the
  4367.  individual window functions. In addition to the WM_UPDATEFRAME and
  4368.  WM_FORMATFRAME processing in MpmFnwpFrame, several of the frame controls
  4369.  have special initialization in their window functions. Scroll bars have the
  4370.  most work to do, so let's take a closer look at how they are created and
  4371.  positioned.
  4372.  
  4373.  The code for scroll bars and scrolling is in MpmScroll.c. The scroll bar
  4374.  window function, MpmFnwpScrollBar (see Figure 10), creates each Macintosh
  4375.  scroll bar control when it receives the WM_CREATE during WinCreateWindow.
  4376.  It uses a straightforward call to the Mac's NewControl function, except for
  4377.  one little trick. NewControl does not have a parameter by which you can
  4378.  tell it whether you want a vertical or horizontal scroll bar. Instead, it
  4379.  looks at the scroll bar dimensions, and whichever direction is
  4380.  larger──vertical or horizontal──is the kind of scroll bar you get. So,
  4381.  MpmFnwpScrollBar makes up an arbitrary rectangle to insure that NewControl
  4382.  creates the right kind of scroll bar. (Remember that the window may be
  4383.  created with zero height and width, and the Mac wouldn't know if it should
  4384.  be a vertical or horizontal scroll bar.) If the window is later resized, you
  4385.  might get the wrong kind of scroll bar if it is sized too small in either
  4386.  direction, but as long as window sizing has a large enough minimum bound,
  4387.  this isn't a problem.
  4388.  
  4389.  These "made up" dimensions aren't the correct ones, but that's no problem,
  4390.  because the scroll bar is still invisible. Before it is displayed, along
  4391.  come the WM_MOVE and WM_SIZE messages to clean things up. Their processing
  4392.  is straightforward, simply calling the Mac's MoveControl and SizeControl
  4393.  functions. With the scroll bar now properly positioned, the WM_SHOW
  4394.  message that comes along next can call the Mac's ShowControl function to
  4395.  make the scroll bar visible.
  4396.  
  4397.  One subtle point here is that a scroll bar that is created via
  4398.  WinCreateStdWindow actually gets the WM_MOVE and WM_SIZE messages twice. The
  4399.  scroll bar, along with the other frame controls, is created with a zero size
  4400.  during the WM_UPDATEFRAME processing. The first WM_MOVE and WM_SIZE
  4401.  messages are sent at that time. Next the WM_FORMATFRAME processing makes a
  4402.  call to WinSetMultWindowPos to set the actual positions, sending a second
  4403.  pair of WM_MOVE and WM_SIZE messages. This is not that uncommon a situation
  4404.  in PM; windows are often created with zero size then sized before being made
  4405.  visible, and the PM documentation recommends that WM_SIZE processing be
  4406.  skipped if the size is zero.
  4407.  
  4408.  The menu and system menu windows also have their work cut out for them, but
  4409.  it's handled a little differently. For one thing, they don't occupy any
  4410.  space in the Macintosh window-they are child windows only for the sake of
  4411.  sending and receiving messages, not because they are visible as windows. The
  4412.  menu bar, of course, is visible at the top of the Macintosh screen, and it
  4413.  certainly doesn't look like a child of the frame window. It really is a
  4414.  child window, however, since WinQueryWindow (hwndFrame, FID_MENU, FALSE )
  4415.  will find it, and you can send messages to it and your client window will
  4416.  receive WM_COMMAND and other messages from it.
  4417.  
  4418.  The idea of using an invisible child window──or an invisible window of any
  4419.  kind──may seem strange at first. Of course, in this particular case, I had
  4420.  no choice; I wanted MacPM to emulate PM, but with the Macintosh menu style
  4421.  at the top of the screen. The invisible child window is about the only way
  4422.  to accomplish that. Invisible windows can be very handy in other situations,
  4423.  though. The mere fact that you can send messages to them and vice versa
  4424.  gives you an opportunity to do something approaching object-oriented
  4425.  programming.
  4426.  
  4427.  On the menu window code, I really cheated. In PM there's a full set of menu
  4428.  messages that let you manipulate menus dynamically, most of which would be
  4429.  simple enough to implement on the Mac──with the caveat that the Mac has only
  4430.  the single menu bar. Either the application can have only one window with a
  4431.  menu bar (as an ASSERT in MacPM enforces now), or you would have to come up
  4432.  with a scheme for merging the various windows' menus into the one menu bar.
  4433.  For that kind of application, the best approach would be to write it as an
  4434.  MDI window. There's a very convenient parallel between the MDI window with
  4435.  its menu bar and overlapping child windows, and the Macintosh screen with
  4436.  its menu bar at the top and overlapping windows below. In fact, as I write
  4437.  this I am running MindWrite and Lightspeed C on the Mac, and each one has a
  4438.  "Window" menu at the end, just like MDI. (The Mac applications actually
  4439.  call it "Windows," but that's close enough for rock 'n' roll.)
  4440.  
  4441.  Nice as this daydreaming can be, there's none of it in this version of
  4442.  MacPM. Instead, WinCreateStdWindow calls MpmMenuLoad (see Figure 11)──found
  4443.  in MpmMenu.c──if the FCF_MENU flag is set. MpmMenuLoad does some very simple,
  4444.  generic Mac menu initialization. It loads in the menu bar definition from
  4445.  the resource file, looks for an Apple menu and adds the desk accessory menu
  4446.  to it, then displays the menu bar.
  4447.  
  4448.  The last frame control with special initialization is the title bar. If the
  4449.  FCF_TITLEBAR flag is set, WinCreateStdWindow calls WinSetWindowText to pass
  4450.  the pszTitle parameter along to the title bar control. Next,
  4451.  WinSetWindowText sends a WM_SETWINDOWPARAMS message to the window in
  4452.  question, with the WPM_TEXT and WPM_CCHTEXT flags and the corresponding
  4453.  fields in the WNDPARAMS structure set. The title bar window function,
  4454.  MpmFnwpTitleBar, responds to WM_SETWINDOWPARAMS by calling the Mac's
  4455.  SetWTitle function to set the displayed window title. MpmFnwpTitleBar is
  4456.  also able to respond to WM_QUERYWINDOWPARAMS, getting the window title with
  4457.  the Mac's GetWTitle function and passing it back to the caller.
  4458.  
  4459.  The only complication in these messages is that the text strings must be
  4460.  converted back and forth between the C and Pascal string formats. Mac
  4461.  programmers working in C quickly get used to this since the Mac Toolbox
  4462.  functions all expect (and return) Pascal-style strings, which have no zero
  4463.  terminator but instead have a length byte at the front. Lightspeed C does
  4464.  have Pascal-style string constants for this situation, but that doesn't
  4465.  help in this case since Sleuth is pure PM-style C code and passes C-style
  4466.  strings into MacPM.
  4467.  
  4468.  The remaining frame controls have no special initialization. They will have
  4469.  some work later, when they receive mouse and other messages, but first, we
  4470.  have to create those messages and get them to their destination. The code
  4471.  for doing this is in MpmMsg.c.
  4472.  
  4473.  
  4474.  Events
  4475.  
  4476.  With MacPM, a message isn't just a message, it's an event. MacPM, just like
  4477.  Windows or the real PM, has event messages along with a host of other kinds
  4478.  of messages. I've discussed several initialization and notification
  4479.  messages, which any PM programmer should be familiar with. These messages go
  4480.  directly to the destination window function via a WinSendMsg call. This
  4481.  is one of the simplest functions in MacPM; other than a validity check on
  4482.  the window handle, all it does is make an indirect function call to the
  4483.  window function. This function address is a cinch to get to and call with
  4484.  the MYWNDOF macro:
  4485.  
  4486.    return ( *MYWNDOF (hwnd).pfnwp )( hwnd, msg, mp1, mp2 );
  4487.  
  4488.  That's WinSendMsg at a glance-rather easy. Now for the fine print. After
  4489.  Sleuth creates its main window and does some other initialization, it falls
  4490.  into a typical PM message loop:
  4491.  
  4492.    while( WinGetMsg( hab, &qmsg, NULL, 0, 0 ) ) WinDispatchMsg( hab, &qmsg );
  4493.  
  4494.  It happens that Mac applications are constructed around a similar main
  4495.  event loop:
  4496.  
  4497.    while( TRUE )
  4498.    {
  4499.      fMine = GetNextEvent( everyEvent, &theEvent );
  4500.      MyDispatchEvent( &theEvent );
  4501.    }
  4502.  
  4503.  GetNextEvent is a Mac Toolbox function that returns the next event, very
  4504.  much like WinGetMsg. (The return value from GetNextEvent tells whether the
  4505.  event occurred in an application window or in a system window, that is, a
  4506.  desk accessory.) MyDispatchEvent isn't part of the Mac Toolbox, but a
  4507.  function that would be part of this imaginary application to take care of
  4508.  the incoming events. Before GetNextEvent picks them up, the Mac events are
  4509.  held in an event queue much like PM's message queue.
  4510.  
  4511.  The Mac provides events for the following conditions: mouse down and up, key
  4512.  down and up (and key repeat), window update and activate/deactivate. These,
  4513.  of course, correspond closely to PM messages. The Mac has a few other events
  4514.  that don't map well into any PM messages, so MacPM disregards them.
  4515.  
  4516.  For the events that have corresponding messages, things are almost
  4517.  straightforward. For instance, when we click the mouse in Sleuth's client
  4518.  window we go into the main message loop, inside the WinGetMsg function.
  4519.  Since WinGetMsg isn't supposed to return until it has a message for us, it
  4520.  sits in its own idle loop:
  4521.  
  4522.    while( ! WinPeekMsg( hab, pqmsg, hwnd, msgMin, msgMax, PM_REMOVE ) )
  4523.    WinWaitMsg( hab, msgMin, msgMax );
  4524.  
  4525.  Here's the tricky part: WinWaitMsg is the function that is finally supposed
  4526.  to wait for a message; and sure enough, it is indeed waiting in yet another
  4527.  idle loop:
  4528.  
  4529.    while( ! WinPeekMsg( hab, &qmsg, NULL, msgMin, msgMax, PM_NOREMOVE ) );
  4530.  
  4531.  Note the difference between those two WinPeekMsg calls. The PM_REMOVE option
  4532.  in the first one tells WinPeekMsg to pull the message from the queue,
  4533.  because that's what WinGetMsg is supposed to do. The second one has the
  4534.  PM_NOREMOVE option, because WinWaitMsg is supposed to wait until a message
  4535.  is available and then return, but with the message still in the queue. So
  4536.  you can see that WinPeekMsg gets called somewhat redundantly during the
  4537.  course of a single WinGetMsg call──first, inside WinWaitMsg to find out that
  4538.  the message is available, then again inside WinGetMsg to actually pull the
  4539.  message. While this isn't very efficient, it certainly made the coding
  4540.  easier──once I had both options of WinPeekMsg debugged, the other message
  4541.  functions fell together with the simple code you see above.
  4542.  
  4543.  WinPeekMsg is where life becomes interesting. It helps that Sleuth doesn't
  4544.  use the filtering options of WinPeekMsg, with which you can specify the
  4545.  minimum and maximum message numbers along with the window handle you're
  4546.  interested in. The window handle would be no problem, but implementing
  4547.  the message numbers would be a nuisance. There is one special case
  4548.  internally in MacPM: WinSetActiveWindow calls WinPeekMsg specifically
  4549.  looking for WM_ACTIVATE messages only so it can have the WM_ACTIVATE sent
  4550.  to the window at the proper time. The Mac normally queues this event.
  4551.  WinPeekMsg handles this as a special case, setting a different event mask
  4552.  for the Mac Toolbox call. The event mask tells which events you want to
  4553.  receive.
  4554.  
  4555.  Before asking for any events, WinPeekMsg calls the Mac's SystemTask
  4556.  function. This yields control to desk accessories (and under MultiFinder,
  4557.  to other applications). Then WinPeekMsg calls either GetNextEvent or
  4558.  EventAvail, depending on whether you specified PM_REMOVE or PM_NOREMOVE.
  4559.  (EventAvail is the same as GetNextEvent except it leaves the event in the
  4560.  queue.) If an event is returned, WinPeekMsg then calls one of several
  4561.  internal functions to convert the event into the equivalent PM message,
  4562.  which then gets passed back to whatever called WinPeekMsg.
  4563.  
  4564.  
  4565.  Mousing Around
  4566.  
  4567.  Mouse movement requires special treatment. The Mac does not have a mouse-
  4568.  move event; it has events for mouse-down and mouse-up, but mouse movement
  4569.  doesn't generate an event. However, when no event is pending at all,
  4570.  GetNextEvent returns a special null event. Since the mouse position is
  4571.  passed along with every event, as in PM, whether or not the event has to do
  4572.  with the mouse, it's not too much trouble for WinPeekMsg to check the mouse
  4573.  position when it gets a null event. If the mouse has moved, a WM_MOUSEMOVE
  4574.  message is generated. If not, WinPeekMsg just returns FALSE to indicate no
  4575.  message is available.
  4576.  
  4577.  Mouse clicks are easier to handle than mouse movement-single clicks are,
  4578.  anyway. The Mac provides mouse-down and mouse-up events, so it's simply a
  4579.  matter of passing these through as PM messages. Double clicks are a little
  4580.  more complicated. Like many things on the Mac, there is no automatic
  4581.  support for these; the application must determine whether two mouse clicks
  4582.  are close enough in time and space to be a double-click. MacPM should
  4583.  compare the time and location of each mouse-down event with the most recent
  4584.  mouse-up event and, if they are close enough, generate a WM_BUTTON1DBLCLK
  4585.  message instead of a WM_BUTTON1DOWN. I say "should" because this isn't coded
  4586.  in this version of MacPM.
  4587.  
  4588.  Each one of these mouse events is passed through the MpmMsgMouse function
  4589.  (see Figure 12), which decides which window gets the message. First, it
  4590.  determines the proper main Mac window with a call to the Mac's FindWindow
  4591.  function. This not only tells which window the mouse was in, but which area,
  4592.  such as the size box or the close box. It also distinguishes mouse clicks
  4593.  in a system window (that is, a desk accessory) from those on the desktop
  4594.  itself. If the mouse is in one of our windows, the mouse message has to be
  4595.  passed to the correct child window. Some return codes from FindWindow
  4596.  indicate this directly, like inGrow, which indicates the mouse is in the
  4597.  size box. In these cases, the proper child window is determined directly
  4598.  from the return code (for example, the FID_SIZEBORDER window for inGrow).
  4599.  The inContent return code takes a little more work. The MpmMsgFindChild
  4600.  function does the trick here; it scans through all the child windows of the
  4601.  main window, looking for a match on the child window rectangles. The
  4602.  appropriate child window then gets the mouse message, or the frame window
  4603.  gets it, if no child window matches.
  4604.  
  4605.  In theory, there should be some WM_HITTEST messages flying around at this
  4606.  point. I didn't bother with these since things were working pretty well
  4607.  without them and, to be honest, I wasn't sure from the preliminary PM
  4608.  documentation just how WM_HITTEST was supposed to work. An application
  4609.  using child windows in a more complex way might need the WM_HITTEST
  4610.  messages.
  4611.  
  4612.  Keyboard and mouse messages require some translation to convert them to the
  4613.  proper PM form, but this is nothing special. Mouse messages do need
  4614.  translation from the Mac's coordinate system to PM's upside down
  4615.  system-which is a minor nuisance, but one that crops up several places in
  4616.  the code. The mapping functions in MpmMap.c take care of this where
  4617.  necessary.
  4618.  
  4619.  
  4620.  The Active Life
  4621.  
  4622.   The Mac's concept of active and inactive windows is very much the same as
  4623.  that in PM and Windows; the topmost visible main window is the active
  4624.  window, and all others are inactive. All windows in each of these systems
  4625.  are "active" in the sense that the application may display information in
  4626.  them, but the topmost window has a different visual appearance to
  4627.  highlight it. The Mac, however, places greater emphasis on the difference
  4628.  between active and inactive windows; the scroll bars and size box disappear
  4629.  on inactive windows, and a mouse click in an inactive window brings the
  4630.  window to the top but is otherwise disregarded. Unlike Windows and PM──in
  4631.  which you click in any visible portion of any window, and it does what you
  4632.  expect──the Mac takes a second click to "really" click the mouse in the
  4633.  window. This is a flaw in the original Macintosh user interface guidelines,
  4634.  and not all Mac applications follow it; some work like PM and Windows,
  4635.  making all windows respond immediately to clicks. The Finder(TM), for
  4636.  example, works partially this way. But I followed the majority and made
  4637.  mouse clicks in MacPM work according to the guidelines, even though it's
  4638.  less convenient. This is certainly something to consider changing; I don't
  4639.  think Mac users would object if their windows were more responsive.
  4640.  
  4641.  In any case, MpmMsgActivate processes the Mac's activate event, turning it
  4642.  into a WM_ACTIVATE message with the appropriate activate/deactivate flag.
  4643.  There is a little trick in this function which shows up in several others as
  4644.  well. Don't forget that WinPeekMsg can be called with either the PM_REMOVE
  4645.  or PM_NOREMOVE option. If called with PM_NOREMOVE, then later it will be
  4646.  called again to pick up the same message with the PM_REMOVE option. So the
  4647.  little trick is that, in the PM_NOREMOVE case, MpmMsgActivate does nothing
  4648.  more than return the PM message. When called again with the PM_REMOVE
  4649.  option, it goes ahead and updates the grow icon and scroll bars to reflect
  4650.  the new active/inactive state.
  4651.  
  4652.  
  4653.  Painting
  4654.  
  4655.  Update events are the trickiest ones of all. These generate WM_PAINT
  4656.  messages, of course, but an update event pertains to an entire Macintosh
  4657.  window; the Mac doesn't know we have child windows inside it. Therefore
  4658.  MacPM must parcel out the WM_PAINT messages to the child windows, making
  4659.  sure each one has the right coordinate system and clipping region. This is
  4660.  done in MpmPaint.c, and it's where I really started to wonder if child
  4661.  windows are worth it. To be honest, I could have gotten by with less work on
  4662.  the child window painting for this particular application. But I wanted to
  4663.  find out what's involved in emulating the individual WM_PAINT messages that
  4664.  get sent to child windows in PM.
  4665.  
  4666.  MpmMsgPaint is where it all starts, and it's simple enough; it just calls
  4667.  WinUpdateWindow, once it determines that the PM_REMOVE flag was set on the
  4668.  original WinPeekMsg call. WinUpdateWindow is the beginning of some serious
  4669.  cheating. In the real PM, you can call WinUpdateWindow individually on
  4670.  any child window, and that window will be painted immediately, while any
  4671.  other pending updates remain deferred. The problem here is that the Mac
  4672.  has BeginUpdate and EndUpdate functions that perform the same tasks as the
  4673.  functions WinBeginPaint and WinEndPaint in PM. However, these functions
  4674.  apply to the entire Macintosh window; they know nothing of child windows.
  4675.  I could have mimicked the PM operation exactly, but it was simpler to say
  4676.  that any WinUpdateWindow call updates the entire Macintosh window,
  4677.  including all child windows.
  4678.  
  4679.  Given this restriction, WinUpdateWindow does the BeginUpdate, paints the
  4680.  grow icon and all controls (for example, the scroll bars) with Mac Toolbox
  4681.  calls, then calls MpmPaintWindow to send WM_PAINT messages to the frame
  4682.  window and all child windows. Note that these are sent, not posted, since
  4683.  they must all be mimicked before the EndUpdate call found at the
  4684.  end of WinUpdateWindow. MpmPaintWindow is a simple recursive function that
  4685.  sends a WM_PAINT message to a window and then calls itself for each first-
  4686.  level child of that window, thus sending WM_PAINT to all child windows at
  4687.  any level of nesting.
  4688.  
  4689.  Now the fun begins. The WM_PAINT we're most interested in is the one sent
  4690.  to Sleuth's client window function. Like all good WM_PAINT handlers (at
  4691.  least ones using the cached micro-PS), this begins with a WinBeginPaint
  4692.  call, which returns the presentation space handle for use in painting. MacPM
  4693.  has to do a bit of work to create this PS, even though a MacPM presentation
  4694.  space barely qualifies as a nano-PS, much less a micro-PS. For that matter,
  4695.  MacPM's "cache" of PS's is awfully tiny; only a single presentation space is
  4696.  reused whenever WinGetPS (see Figure 13) is called.
  4697.  
  4698.  WinBeginPaint itself is easy enough. It just calls WinGetPS and then
  4699.  converts the Mac's visible region bounding rectangle to PM coordinates.
  4700.  (During update processing, the visible region is clipped down to the former
  4701.  update region.) Then WinGetPS copies the entire GrafPort structure from the
  4702.  specified Mac window (or the Mac desktop) into the new PS. A GrafPort is
  4703.  the Mac's structure that corresponds roughly to a device context in PM or
  4704.  Windows. In addition to copying the structure itself, a couple of CopyRgn
  4705.  calls are required to duplicate the visRgn and the clipRgn from the original
  4706.  GrafPort. Once that's done, the visRgn in this new GrafPort must be clipped
  4707.  down further to take the child window coordinates into account. (I could
  4708.  have left the visRgn alone and modified the clipRgn instead, and perhaps
  4709.  avoided the need to copy the GrafPort, but it seemed simpler and safer this
  4710.  way.)
  4711.  
  4712.  That's not hard in this version of MacPM, since it doesn't support the
  4713.  WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles. However, it does support
  4714.  WS_PARENTCLIP, or more significantly, the absence of WS_PARENTCLIP. If
  4715.  WS_PARENTCLIP is set on a child window, the window lacks its own clipping
  4716.  region, therefore using that of the parent, and it's up to the child window
  4717.  to avoid drawing outside its boundaries. When WS_PARENTCLIP is missing, the
  4718.  child window needs its own clipping region, so WinGetPS takes the
  4719.  intersection of the parent's clipping region and the child window's
  4720.  rectangle and makes that the new clipping region for the child.
  4721.  
  4722.  If WS_CLIPCHILDREN and WS_CLIPSIBLINGS were supported, they would be
  4723.  handled next. Each would basically be a loop over the window's children or
  4724.  siblings, subtracting the appropriate rectangles from the window's
  4725.  clipping region. Since Sleuth doesn't use either of these window styles, the
  4726.  easiest way to implement this was to just say "no".
  4727.  
  4728.  Now you see why WinGetPS makes a copy of the GrafPort it needs. With all of
  4729.  this modification to the clipping region, as well as other possible
  4730.  modifications we would need to make if we wanted to implement additional
  4731.  PM features, the safest thing was to make a private copy of the whole
  4732.  structure. There are probably plenty of optimizations that could be made
  4733.  but, more importantly, it works.
  4734.  
  4735.  That's our presentation space such as it is; so now what does Sleuth do with
  4736.  it? Just two things: a GpiErase and a series of GpiCharStringAt calls. In
  4737.  MpmGPI.c (see Figure 14) these functions were remarkably simple to
  4738.  implement, because all the dirty work was already done. GpiErase is nothing
  4739.  more than a call to the Mac function EraseRgn, passing it the visRgn from
  4740.  our modified GrafPort in the pico-PS. GpiCharStringAt breaks down into
  4741.  GpiMove──which calls the Mac's MoveTo function──and GpiCharString, which is
  4742.  a simple call to the Mac's DrawText function.
  4743.  
  4744.  Although they took a lot of work, the simplicity of these GPI functions
  4745.  gives me hope that other simple GPI drawing functions would also be easy to
  4746.  implement on the Mac. Some of the fancier GPI stuff would, I suspect, be a
  4747.  lot of work since the Mac doesn't have exact equivalents.
  4748.  
  4749.  When Sleuth is done painting its client window, it calls WinEndPaint, which
  4750.  does nothing more than make a call to WinReleasePS, which in turn just frees
  4751.  up the pico-PS by discarding its regions and marking it as no longer in
  4752.  use.
  4753.  
  4754.  One other topic in window painting is invalidation. Sleuth does call the
  4755.  WinInvalidateRect function whenever it wants to force its client window to
  4756.  be repainted, and this function is very straightforward. It just converts
  4757.  the rectangle to a Mac rectangle and then calls the Mac's InvalRect
  4758.  function. It's then up to the Macintosh event manager to generate an update
  4759.  event.
  4760.  
  4761.  After all of this discussion about WM_PAINT, one strange fact is that none
  4762.  of the window functions in MacPM itself provide much in the way of WM_PAINT
  4763.  processing. Since the frame control windows all happen to be Macintosh
  4764.  "controls" as well (or invisible), they were all drawn back at the
  4765.  beginning of WinUpdateWindow, when it called the Mac's UpdtControl
  4766.  function. They don't have to do anything else.
  4767.  
  4768.  The only window function that handles WM_PAINT is the frame window
  4769.  function, MpmFnwpFrame, which does not really paint anything; the Mac
  4770.  Toolbox takes care of the drawing of the window frame. It does erase the
  4771.  client area background, by sending a WM_ERASEBACKGROUND message to the
  4772.  client window. The client window can either erase the background itself on
  4773.  this message and return TRUE, or else return FALSE and let the frame window
  4774.  do the erasing. This is done with a GpiErase call, bracketed by WinGetPS and
  4775.  WinReleasePS. This WM_ERASEBACKGROUND processing doesn't really achieve the
  4776.  purpose that it does in PM, which is to cause synchronous background
  4777.  erasing in an asynchronously painted client window──because I neatly
  4778.  sidestepped the whole issue of synchronous vs. asynchronous painting.
  4779.  MacPM uses its own strange brew when it comes to window painting.
  4780.  
  4781.  
  4782.  Scrolling
  4783.  
  4784.  Because Sleuth's windows are scrollable, MacPM must provide the two pieces
  4785.  of PM window scrolling: scroll bar tracking and messages, and the
  4786.  WinScrollWindow function. Scroll bars on the Mac are a pain to deal with.
  4787.  Not only must you position them yourself──there's no asking for and getting
  4788.  a standard scroll bar──but your code must get much more involved in tracking
  4789.  them. Unlike PM and Windows, in which a scroll bar can track itself and send
  4790.  messages to its owner, application code must call the proper tracking
  4791.  function (depending on which part of the scroll bar was clicked) and deal
  4792.  with more of the busywork itself. Sleuth, being a PM application, doesn't
  4793.  have to worry about this, but MacPM does. MpmScroll.c has all the details.
  4794.  
  4795.  When the mouse is clicked in a scroll bar, this sends a WM_BUTTON1DOWN
  4796.  message to MpmFnwpScrollBar. After converting the PM mouse coordinate to a
  4797.  Mac point, this calls the Mac's FindControl function, which identifies the
  4798.  Mac control containing that point, and which part of the control it is. We
  4799.  already know which Mac control should be returned here, since the
  4800.  WM_BUTTON1DOWN was here initially because it hit this particular child
  4801.  window, and MacPM's window structure for a control includes the Mac control
  4802.  handle. So this is a good place for an ASSERT macro to trap out, if for some
  4803.  reason FindControl returned a different control from what we expected. (You
  4804.  will find a lot of ASSERTs in MacPM; they are an invaluable debugging
  4805.  tool.)
  4806.  
  4807.  The piece of information we are really after from the FindControl call is
  4808.  the "part code," which tells whether the mouse was in the thumb, page
  4809.  up/down area, or one of the arrows. We need to know whether the mouse is in
  4810.  the thumb or not because we're about to call the Mac's TrackControl
  4811.  function, one of the more bizarre functions in the Mac Toolbox. It does
  4812.  the actual mouse tracking, either dragging a gray rectangle for the thumb or
  4813.  monitoring mouse movement and release in the arrows or page areas.
  4814.  
  4815.  TrackControl looks simple enough; you pass it the control handle, mouse
  4816.  position, and a pointer to a callback function that is called repeatedly
  4817.  until the mouse is released. The weird thing is that you have to use two
  4818.  different callback functions, one for thumb tracking and one for everything
  4819.  else, and they take different sets of parameters. The arrow and page
  4820.  callback is reasonably straightforward; it receives as parameters the
  4821.  control handle and part code, and it only gets called if the mouse is
  4822.  inside the proper tracking limit rectangle (that is, the arrow or the page
  4823.  area). In MacPM this function, MpmTrackScrollBar, just sends a WM_HSCROLL
  4824.  or WM_VSCROLL message to its owner, with the SB_ command code determined by
  4825.  the Mac's part code. Then as in PM it's up to the owner to set the new
  4826.  scroll bar position and do any needed window scrolling.
  4827.  
  4828.  The thumb tracking callback is where things get difficult. This function has
  4829.  no parameters at all; it must figure everything out by reading the mouse
  4830.  position each time it's called. The function has to calculate the new
  4831.  scroll bar value itself, based on how far the mouse has moved and on the
  4832.  original scroll bar value and range. This isn't a difficult calculation, but
  4833.  it's not trivial, and it's silly to require the application to calculate it,
  4834.  given that TrackControl is capable of calculating the final scroll bar
  4835.  position when the mouse button is released. It should have made this same
  4836.  calculation during tracking and passed it to the thumb tracking function as
  4837.  a parameter.
  4838.  
  4839.  But that's not the worst of it. You've probably noticed that when you drag a
  4840.  scroll bar thumb──on PM, Windows, or the Mac──it will continue to track if
  4841.  the mouse is within a certain distance of the scroll bar, but it jumps back
  4842.  to its original position if you move too far. Move it closer and it resumes
  4843.  tracking. TrackControl takes care of this as far as the visual aspect. But
  4844.  does the thumb tracking callback know whether tracking is suspended because
  4845.  the mouse is too far away? Well, it can just compare the new mouse position
  4846.  with...what? Dumb question. I forgot that the thumb tracking callback doesn't
  4847.  get told anything.
  4848.  
  4849.  Although I haven't disassembled TrackControl, I suspect it's calling
  4850.  another Toolbox function called DragGrayRgn, which tracks the mouse and
  4851.  drags a gray outline of a rectangle or other region. One of the parameters
  4852.  to this function is a "slop rectangle," which determines how far the mouse
  4853.  may stray before tracking is suspended. This slop rectangle would be just
  4854.  the thing the thumb callback needs, but it's stored somewhere on the stack,
  4855.  and is inaccessible to the thumb callback.
  4856.  
  4857.  Need I say that after much experimentation I finally gave up on including a
  4858.  thumb tracking function in MacPM? The only harm done is that the
  4859.  SB_SLIDERTRACK notification isn't supported, so Sleuth does not scroll
  4860.  its window during thumb tracking, only afterward. If you've ever wondered
  4861.  why nearly all Mac applications wait until thumb tracking is complete before
  4862.  actually scrolling their windows, this is why. At least 99 percent of the
  4863.  Mac programmers in the world came to the same conclusion I did: thumbs
  4864.  down.
  4865.  
  4866.  Even without worrying about thumb tracking, there's one more minor
  4867.  complication with the thumb. When you release the mouse button, TrackControl
  4868.  calculates the new scroll bar position and sets the scroll bar to that
  4869.  position. However in PM, when the SB_SLIDERPOSITION notification is sent,
  4870.  the new position has not yet been set. An SBM_QUERYPOS message returns the
  4871.  old position, but it's the responsibility of the application to set the new
  4872.  position. MpmFnwpScrollBar handles this by saving the old thumb position in
  4873.  a local static variable at the beginning of thumb tracking, at which time
  4874.  the local static flag fTrackThumb is also set, including the moment the
  4875.  SB_SLIDERPOSITION is sent. Then it's a simple matter for the SBM_QUERYPOS
  4876.  code to check fTrackThumb and return either the old value or the current
  4877.  value (in the normal case). When Sleuth follows up by sending an SBM_SETPOS
  4878.  message, the position has already been set, but the redundant call does no
  4879.  harm.
  4880.  
  4881.  Enough complaining about scroll bars. The other part of scrolling,
  4882.  WinScrollWindow, is a lot simpler. Sleuth calls this function when it is
  4883.  ready to actually scroll its client window. WinScrollWindow first checks for
  4884.  child windows and adjusts their positions if the SW_SCROLLCHILDREN flag is
  4885.  set. (There aren't any child windows inside Sleuth's client window, so it
  4886.  doesn't use SW_SCROLLCHILDREN.) Then it picks up the window rectangle for
  4887.  the window to be scrolled, converts it to Mac coordinates, and passes the
  4888.  resulting rectangle to the Mac's ScrollRect function. This function scrolls
  4889.  the bits in the window and then calculates the invalidated region. It
  4890.  doesn't actually invalidate the region and cause it to be repainted, it
  4891.  merely calculates it. So, WinScrollWindow then calls the Mac's InvalRgn
  4892.  function to accumulate this region into the window's update region.
  4893.  
  4894.  
  4895.  Movement and Sizing
  4896.  
  4897.  Even though the child windows for FID_TITLEBAR and FID_SIZEBORDER aren't
  4898.  actually visible in MacPM, their window functions (in MpmFrame.c) provide
  4899.  the same services as in PM itself. In each case, the WM_BUTTON1DOWN message
  4900.  will cause the appropriate mouse tracking and window movement or sizing.
  4901.  MpmMsgMouse directs this message to the FID_TITLEBAR or FID_SIZEBORDER
  4902.  window when the Mac's FindWindow function returns the inDrag or inGrow
  4903.  window area code. For FID_TITLEBAR, the MpmFnwpTitleBar function takes the
  4904.  message and converts the mouse location back to Mac coordinates, then calls
  4905.  the Mac's DragWindow function. This function performs the mouse tracking and
  4906.  actually moves the window when the button is released, if the mouse was
  4907.  inside a bounding rectangle that was passed to DragWindow. MpmFnwpTitleBar
  4908.  calculates this bounding rectangle somewhat arbitrarily, four pixels inside
  4909.  the actual screen size (not including the menu bar). After the button is
  4910.  released, MpmFnwpTitleBar checks to see if the window actually did move, and
  4911.  if so, sends a WM_MOVE message to its owner (the frame window).
  4912.  
  4913.  Window sizing is similar, but a little more complicated. MpmFnwpSizeBorder
  4914.  takes the WM_BUTTON1DOWN message in this case, and calls the Mac's
  4915.  GrowWindow function to do the mouse tracking. Unlike DragWindow, GrowWindow
  4916.  doesn't actually resize the window──it just returns the new mouse position.
  4917.  Then MpmFnwpSizeBorder picks up the old frame window size and position by
  4918.  using WinQueryWindowPos and sets the new size and position with
  4919.  WinSetMultWindowPos. WinSetMultWindowPos is more convenient than
  4920.  WinSetWindowPos in this particular case, since it takes an SWP structure
  4921.  that is exactly like the one filled in by WinQueryWindowPos.
  4922.  WinSetMultWindowPos takes care of sending the WM_MOVE and WM_SIZE messages
  4923.  to the frame window, which in turn sends itself a WM_FORMATFRAME message to
  4924.  reposition the frame controls and client window. Before calling
  4925.  GrowWindow, MpmFnwpSizeBorder should send the frame window a
  4926.  WM_QUERYMINMAXINFO message, but instead it merely sets arbitrary tracking
  4927.  limits: 100-pixel minimum height and width, no maximum limit.
  4928.  
  4929.  
  4930.  Menu Selection
  4931.  
  4932.  The menu window function MpmFnwpMenu, located in MpmMenu.c, does triple duty
  4933.  because there are three different "menus" to consider: the menu bar, the
  4934.  system menu (close box on the Mac), and the minimize/maximize icons (zoom
  4935.  box on the Mac). In PM each of these is a true menu and they are all treated
  4936.  the same. In MacPM the close box and zoom box are special cases but, to be
  4937.  consistent with PM, they are all handled in MpmFnwpMenu. MpmMsgMouse
  4938.  directs the WM_BUTTON1DOWN message to one of these three frame controls and,
  4939.  since they are all of class WC_MENU, MpmFnwpMenu gets the message in each
  4940.  case.
  4941.  
  4942.  For FID_SYSMENU (the close box), it's straightforward. MpmFnwpMenu simply
  4943.  calls the Mac's TrackGoAway function and, if that function returns TRUE,
  4944.  then it sends a WM_SYSCOMMAND message with the SC_CLOSE command to its
  4945.  owner, the frame window. MpmFnwpFrame passes this command message through to
  4946.  the client window, and Sleuth's client window function merely passes it on
  4947.  to WinDefWindowProc, which posts a WM_QUIT message to the application
  4948.  queue.
  4949.  
  4950.  FID_MINMAX (the zoom box) is almost as simple, but I cheated a little with
  4951.  this one. Back in MpmMsgMouse, the FindWindow function returned two
  4952.  different area codes for the zoom box, inZoomIn or inZoomOut, depending on
  4953.  whether the window is currently zoomed or not. The Mac provides a simple
  4954.  ZoomWindow function to do the actual zooming or un-zooming, but that
  4955.  function expects to receive the inZoomIn or inZoomOut code to tell it which
  4956.  to do. So, I simply passed that code along in the high-order word of mp2 in
  4957.  the WM_BUTTON1DOWN message. That word happens to be unused in PM, so I was
  4958.  able to get away with this nonstandard use. Given that little trick,
  4959.  MpmFnwpMenu calls the Mac's TrackBox function and if the function returns
  4960.  TRUE, sends either an SC_RESTORE or SC_MAXIMIZE message to its owner, the
  4961.  frame window. MpmFnwpFrame takes over from there, calling the Mac's
  4962.  ZoomWindow function and sending the appropriate WM_MOVE and WM_SIZE
  4963.  messages.
  4964.  
  4965.  There's one more thing that MpmFnwpMenu has to deal with and that is the
  4966.  menu bar itself. When the mouse button is pressed in the menu bar,
  4967.  MpmMsgMouse directs the WM_BUTTON1DOWN message to the FID_MENU control
  4968.  window. MpmFnwpMenu sends a WM_INITMENU message to its owner, then calls
  4969.  the Mac's MenuSelect function to do the mouse tracking. This function
  4970.  returns the Mac menu ID; the high-order word is the resource ID for the
  4971.  pull-down menu, and the low-order word tells which item was selected on the
  4972.  menu. Unfortunately, while resource IDs may be chosen as desired, the menu
  4973.  item number in the low-order word is simply a sequential index into the
  4974.  menu. This is different from PM or Windows, where you can assign your own
  4975.  menu IDs to each menu item. It means that if you want to move a menu item to
  4976.  a new position, you must update your definitions in the C code and
  4977.  recompile. For MacPM I could have set up a way to allow arbitrary menu
  4978.  ID's──the way that Windows and PM allow them──but it was simpler to just use
  4979.  the Mac's numbering convention, assigning menu IDs in the PM version that
  4980.  would match the Mac's menu IDs.
  4981.  
  4982.  The code in MpmFnwpMenu has to check one more thing. The Apple menu has a
  4983.  list of desk accessories as well as menu items for the application itself.
  4984.  If the menu selected was a desk accessory, it starts up that desk accessory
  4985.  with an OpenDeskAcc call. Otherwise, it sends a WM_COMMAND message to its
  4986.  owner, the frame window. The frame window function passes this message along
  4987.  to the client window, where Sleuth's window function handles it.
  4988.  
  4989.  
  4990.  Header Files
  4991.  
  4992.  In MacPM I followed the principle that the Presentation Manager C code
  4993.  should run unchanged. I would have liked to apply this to the .H files as
  4994.  well as the .C files, because the OS/2 .H files contain all the necessary
  4995.  declarations. However, because of the compiler differences I mentioned
  4996.  earlier, it didn't quite work out this way. I was able to use the .H files
  4997.  from OS/2 with Lightspeed C, but it took some manual editing to get them to
  4998.  compile. Here's what I had to change:
  4999.  
  5000.  Conditional compilation. ANSI C has a number of new preprocessor features
  5001.  that aren't present in Lightspeed C. The only one used much in the OS/2 .H
  5002.  files is the #if defined(name) construct. This serves the same purpose as
  5003.  the older #ifdef but allows checking for more than one symbol at a time. The
  5004.  OS/2 .H files use this in many places, in a form like the following:
  5005.  
  5006.    #if ( defined (INCL_WINFRAMEMGR) | !defined (INCL_NOCOMMON) )
  5007.  
  5008.  Since I didn't care about the check for INCL_NOCOMMON, I edited them to work
  5009.  with Lightspeed C:
  5010.  
  5011.    #ifdef INCL_WINFRAMEMGR
  5012.  
  5013.  Fortunately most of the conditional compilations were already in the #ifdef
  5014.  form (they only test for a single symbol), so there weren't too many places
  5015.  that needed editing.
  5016.  
  5017.  Prototypes for function pointer declarations. Although Lightspeed C accepts
  5018.  function prototypes, it does not accept them for pointer types, such as:
  5019.  
  5020.    typedef VOID (PASCAL FAR *PFNEXITLIST) ( USHORT );
  5021.  
  5022.  I took care of these with the simple expedient of commenting out the
  5023.  parameter declarations:
  5024.  
  5025.    typedef VOID (PASCAL FAR *PFNEXITLIST) ( /*USHORT*/ );
  5026.  
  5027.  NEAR, FAR, and PASCAL keywords. Since Lightspeed C and Microsoft C both
  5028.  support the "pascal" keyword differently, I decided to forego the use of
  5029.  the Pascal calling sequence. Most uses of the "pascal" keyword in the .H
  5030.  files are coded as PASCAL (which is #define'd as pascal), so I just changed
  5031.  the #define to make PASCAL an empty string. The same thing applies for NEAR
  5032.  and FAR, since they aren't relevant in the Mac environment:
  5033.  
  5034.    #define PASCAL
  5035.    #define NEAR
  5036.    #define FAR
  5037.  
  5038.  One slightly odd thing is that OS2DEF.H fails to use these #define'd symbols
  5039.  in several places, falling back to the lowercase names. This doesn't seem
  5040.  intentional and will hopefully be corrected in a future version of this
  5041.  file. In the meantime, I added two more #define's to effectively disable
  5042.  "near" and "far":
  5043.  
  5044.    #define near
  5045.    #define far
  5046.  
  5047.  I couldn't do this with "pascal", since Lightspeed C needs this keyword for
  5048.  its own .H files, which define all the Mac Toolbox entry points. More
  5049.  manual editing was called for: changing "pascal" to "PASCAL" where it
  5050.  occurred in OS2DEF.H. The other OS/2 header files didn't have this problem.
  5051.  
  5052.  Different calling sequence (order of parameters). Deciding not to use the
  5053.  Pascal calling sequence led to a complication. There are several data
  5054.  structures and macros in PMWIN.H that assume the Pascal calling sequence.
  5055.  The SWP data structure, for example, is defined so that the parameters
  5056.  passed in a call to SetWindowPos can be directly referenced as an SWP
  5057.  structure. To allow this, I reversed the order of the fields in these
  5058.  structures, because the C calling sequence pushes the parameters in the
  5059.  opposite order from the Pascal sequence. Similarly, I had to change the
  5060.  COMMANDMSG, CHARMSG, and MOUSEMSG macros and their associated structures
  5061.  to work with the C calling sequence. Using the C calling sequence was a
  5062.  mixed blessing; it led to extra work but avoided the need to edit every
  5063.  single function declaration to allow for the different positioning of the
  5064.  "pascal" keyword.
  5065.  
  5066.  
  5067.  WinPM
  5068.  
  5069.  As you can see, getting Sleuth running under MacPM was a lot of work. So
  5070.  much, in fact, that I must confess I didn't get a working WinPM written in
  5071.  time for this article. In lieu of actual code, let's take a look at some of
  5072.  the issues involved in putting together a WinPM, starting with window
  5073.  classes.
  5074.  
  5075.  Because Windows resembles PM more than the Mac does, it would be handy to
  5076.  use existing Windows features. I would be tempted to use the window class
  5077.  mechanism in Windows instead of coding it from scratch, as in MacPM. I
  5078.  wonder, however, if this would be a good strategy. Window classes under PM
  5079.  are much simpler than under Windows, with many of the items in the Windows
  5080.  WNDCLASS structure eliminated.
  5081.  
  5082.  Instead, the frame window class and the FCF_ and FS_
  5083.  options in WinCreateStdWindow handle these items. For instance, under
  5084.  Windows all windows of a given class have the same icon, whose handle is
  5085.  stored in the WNDCLASS structure. Under PM, the window class does not
  5086.  determine the icon; each frame window can have its own icon, and it's
  5087.  normally loaded in from the resource file according to the frame window ID.
  5088.  
  5089.  Depending on the application these differences could be glossed over, but
  5090.  Sleuth is a tough cookie with issues like this. It calls functions like
  5091.  WinQueryClassInfo, which have no direct counterpart in Windows. This
  5092.  function is a particular problem. Under Windows, there is no direct way to
  5093.  get at the class information given a class name. You can do it, but you must
  5094.  create an invisible window of the desired class, just to call GetClassWord
  5095.  and GetClassLong to pick up the class information.
  5096.  
  5097.  With problems like this, it may be worthwhile to keep the class support code
  5098.  from MacPM. You would still need to register the window classes with
  5099.  Windows, so there is some redundancy.
  5100.  
  5101.  
  5102.  Messages
  5103.  
  5104.  Message passing is another place where it's tempting to directly use Windows
  5105.  facilities, since window functions are so similar in the two systems. That's
  5106.  what the macro binding system in the Presentation Manager Conversion
  5107.  Guide does.
  5108.  
  5109.  The goal of attempting to maintain "pure" PM code seems to lead to too many
  5110.  problems. For starters, a window function in Windows takes a 16-bit window
  5111.  handle, and its two parameters are different lengths: 16-bit and 32-bit. In
  5112.  PM, all these items are 32-bit. You could fudge the declarations in PMWIN.H
  5113.  to make everything the same length as in Windows, but this still wouldn't
  5114.  handle the differing contents of the message parameters.
  5115.  
  5116.  I'd probably leave the PM window functions alone, with 32-bit window handles
  5117.  and two 32-bit parameters. Unlike MacPM, where all the message processing
  5118.  had to be coded from scratch, this could be handled by having WinPeekMsg
  5119.  call the Windows function PeekMessage and then do the proper conversions.
  5120.  WinDispatchMsg and WinSendMsg would not talk to Windows at all; they would
  5121.  directly call the PM window function, avoiding the problem of different
  5122.  function formats.
  5123.  
  5124.  For messages that are sent from Windows directly to the window function
  5125.  instead of being posted to the queue, the problem is a little different.
  5126.  Instead of giving Windows the address of the PM window function, there
  5127.  would have to be a common window function that Windows would call in all
  5128.  cases. This function would convert the parameters appropriately, find the
  5129.  address of the correct PM window function (probably using space in the
  5130.  "window extra" data), and call the PM window function.
  5131.  
  5132.  
  5133.  Frame Controls
  5134.  
  5135.  Window function differences notwithstanding, it makes sense to create child
  5136.  windows for all the frame controls, just as PM does. I'd take the same
  5137.  approach as in MacPM, where scroll bars are visible child windows in their
  5138.  expected positions, but the title bar, menu bar, and other frame controls
  5139.  are invisible existing only for the purpose of message passing. Windows, of
  5140.  course, already supports child window scroll bars as well as "standard"
  5141.  scroll bars. I would forget about using standard scroll bars; the only extra
  5142.  burden for using child window scroll bars is that their size and position
  5143.  must be explicitly calculated, which is not difficult. For the other frame
  5144.  controls, invisible child windows would make it easy to mimic their
  5145.  operation under PM.
  5146.  
  5147.  
  5148.  Painting/Scrolling
  5149.  
  5150.  With the limited use Sleuth makes of GPI, it's not hard to provide the same
  5151.  functionality by converting the GPI calls into the equivalent GDI calls, the
  5152.  way MacPM does. Also, the window painting logic is much more straightforward
  5153.  than under MacPM. The various shortcuts I mentioned earlier won't be
  5154.  necessary; Windows generates WM_PAINT messages for child windows and top-
  5155.  level windows the same way PM does. A thin layer is needed to take care of
  5156.  the differences in WinBeginPaint vs. BeginPaint, and then I expect the rest
  5157.  would work pretty smoothly.
  5158.  
  5159.  
  5160.  Application or DLL?
  5161.  
  5162.  One simplifying assumption in MacPM was that it had to support only a single
  5163.  application, since the MacPM code is linked in with the application. In
  5164.  Windows this same approach could be used, but the right way to code a WinPM
  5165.  would be as a dynamic-link library (DLL), so it could support more than one
  5166.  application. The mechanics of linking to a DLL are no problem, but a
  5167.  complicating factor is that WinPM would then have to handle multiple
  5168.  applications calling it. This would rule out using some static variables the
  5169.  way MacPM does. Instead, the proper data structures would have to be
  5170.  allocated as needed.
  5171.  
  5172.  One thing that causes problems in Windows DLLs is that they have no way to
  5173.  automatically find out when they are being unloaded. There's a library
  5174.  initialization entry point, but no library termination function.
  5175.  Fortunately the PM API takes care of this with its WinInitialize and
  5176.  WinTerminate functions. On the WinTerminate call-or on
  5177.  WinDestroyMsgQueue-the WinPM DLL can release any resources it has allocated
  5178.  for that application.
  5179.  
  5180.  
  5181.  A Reader Exercise
  5182.  
  5183.  Due to publication deadlines, there are several things I left unimplemented
  5184.  in Sleuth and in WinPM and MacPM. If you would like to try spiffing it up a
  5185.  bit, here are some areas you could improve.
  5186.  
  5187.  The detail windows in Sleuth (see Figure 15) don't exist. To make them work,
  5188.  you'd have to add some code, but this should require little or no change to
  5189.  WinPM and MacPM as far as creating and destroying these windows; the code's
  5190.  already there for that. It would take a little work to get double-click
  5191.  messages from MacPM. This isn't hard, but MpmMsgMouse would have to keep
  5192.  track of the time and position of the last mouse-up, and check each mouse-
  5193.  down to see if it looks like a double click. Then it would simply generate a
  5194.  WM_BUTTON1DBLCLK and not a WM_BUTTON1DOWN. The "Show Detail" menu item
  5195.  should do the same thing as a double-click. This means that Sleuth should
  5196.  have some code for selecting a line with a single click or with the cursor
  5197.  keys. MacPM has to be able to generate keystroke messages for this.
  5198.  
  5199.  There's an "About Sleuth..." menu item, but it doesn't do anything. Dialog
  5200.  boxes are an area that I omitted completely from MacPM and WinPM, so it
  5201.  would take some work (and careful thought) to implement them.
  5202.  
  5203.  One little thing in MacPM that you could fix is the group of items on the
  5204.  Edit menu, which should be grayed out when Sleuth's own windows are active.
  5205.  Those items are really there for desk accessories and should be enabled only
  5206.  when a desk accessory is active. Also, they need to call the SystemEdit
  5207.  function to make them actually work. There's some debugging to do with desk
  5208.  accessories; there are really three different environments they can run in:
  5209.  non-MultiFinder, MultiFinder using the DA Handler layer, and MultiFinder
  5210.  using the application layer. (The last choice is what happens if you hold
  5211.  the Option key down while starting a desk accessory under MultiFinder.) I
  5212.  got desk accessories working in some cases but not in all these situations.
  5213.  
  5214.  I had mentioned earlier a couple of places where the WM_QUERYMINMAXINFO
  5215.  message should be sent, but MacPM and WinPM just choose arbitrary rectangles
  5216.  instead. There's a bug in MacPM where, if a scroll bar is disabled and you
  5217.  click in it, the system seems to freeze until you click the mouse somewhere
  5218.  else ten (yes, ten) times. Then it suddenly catches up with all those
  5219.  clicks. Finally, if you're feeling really ambitious, you could try
  5220.  implementing the SB_SLIDERTRACK message. You'll find some commented-out
  5221.  code in MacPM where I tried to get this one working. Have fun!
  5222.  
  5223.  
  5224.  No Free Lunch
  5225.  
  5226.  As we've seen, there is no easy solution to the problem of portability
  5227.  across windowing environments as disparate as Presentation Manager,
  5228.  Windows, and the Macintosh. Is it worth the work? Perhaps I should ask
  5229.  instead if you can afford not to do it. Each of these systems represents a
  5230.  large (or in the case of Presentation Manager, soon to be very large)
  5231.  market, and people really appreciate having the same or similar software
  5232.  available on both the Mac and PC. The approach I've taken with MacPM and
  5233.  WinPM seems like a fruitful one, but I sure would have liked it if someone
  5234.  else had done the dirty work for me! Depending on your application, a
  5235.  toolkit like XVT might do a good job for you. My personal hope is that C++
  5236.  with some good class libraries will make porting much easier. But I suspect
  5237.  that regardless of how good the tools get, the old saying will still be
  5238.  true: there's no free lunch.
  5239.  
  5240.  
  5241.  Figure 1:  Complete Code Listing for the PM Version of Sleuth
  5242.  
  5243.  SLEUTH.C - Source code listing for Sleuth
  5244.  
  5245.  /* Sleuth.c  */
  5246.  
  5247.  #define INCL_DOSPROCESS
  5248.  #define INCL_GPI
  5249.  #define INCL_WIN
  5250.  #undef NULL
  5251.  
  5252.  #include <os2.h>
  5253.  #include <malloc.h>
  5254.  #include <stdarg.h>
  5255.  #include <stdio.h>
  5256.  #include <stdlib.h>
  5257.  #include <string.h>
  5258.  #include "Sleuth.h"
  5259.  
  5260.  /*-----------------------------------------------------------------*/
  5261.  /*  The display for a window looks like this in collapsed mode:    */
  5262.  /*                                                                 */
  5263.  /*  Window HHHH:HHHH [id] {class} (L,B;R,T) "text"                 */
  5264.  /*                                                                 */
  5265.  /*  or like this in expanded mode:                                 */
  5266.  /*                                                                 */
  5267.  /*      Window handle: HHHH:HHHH   Owner window: HHHH:HHHH         */
  5268.  /*        Class name: {class name}                                 */
  5269.  /*        Window text: "text"                                      */
  5270.  /*        Class style:  HHHHHHHH                                   */
  5271.  /*        Window style: HHHHHHHH                                   */
  5272.  /*        Class function: HHHH:HHHH   Window function: HHHH:HHHH   */
  5273.  /*        Window ID: DDDDD   Process ID: HHHH   Thread ID: HHHH    */
  5274.  /*        Message queue handle: HHHH   Window lock count: DDDDD    */
  5275.  /*        Window extra alloc: DDDDD                                */
  5276.  /*        Window rectangle: Left=D, Bottom=D, Right=D, Top=D       */
  5277.  /*      {blank line}                                               */
  5278.  /*                                                                 */
  5279.  /*  Total number of lines for one window display: 11               */
  5280.  /*-----------------------------------------------------------------*/
  5281.  
  5282.  #define LINES_PER_WINDOW    11
  5283.  #define WINDOW_WIDTH        160
  5284.  
  5285.  /* Structure of information for each window. */
  5286.  
  5287.  #define CLASSMAX    40
  5288.  #define TEXTMAX     40
  5289.  
  5290.  typedef struct {
  5291.      HWND    hwnd;                   /* Window handle */
  5292.      CLASSINFO class;                /* Class info */
  5293.      CHAR    szClass[CLASSMAX];      /* Class name */
  5294.      CHAR    szText[TEXTMAX];        /* Window title or contents */
  5295.      ULONG   flStyle;                /* WS_ window style */
  5296.      HWND    hwndOwner;              /* Owner window handle */
  5297.      PFNWP   pfnwp;                  /* Window function */
  5298.      USHORT  usWindowID;             /* Window ID */
  5299.      PID     ProcessID;              /* Process ID */
  5300.      TID     ThreadID;               /* Thread ID */
  5301.      HMQ     hmq;                    /* Message queue handle */
  5302.      RECTL   rclWindow;              /* Window rect, screen coord. */
  5303.      SHORT   sLockCount;             /* Window lock count */
  5304.      SHORT   sLevel;                 /* Child window nesting level */
  5305.      FLAG    fSelect:1;              /* Is this window selected? */
  5306.      FLAG    fHasText:1;             /* Does the window have text? */
  5307.  } INFO;
  5308.  
  5309.  typedef INFO * PINFO;               /* Pointer to INFO array */
  5310.  
  5311.  /* Static variables. */
  5312.  
  5313.  PIDINFO     pidi;
  5314.  
  5315.  HAB         hab;
  5316.  HMQ         hmq;
  5317.  HWND        hwndDesktop, hwndObject;
  5318.  HWND        hwndSleuth, hwndFrame, hwndHorzScroll, hwndVertScroll;
  5319.  
  5320.  PINFO       pInfoBase;
  5321.  SHORT       sWinCount;              /* # of windows in system */
  5322.  BOOL        fExpand = FALSE;        /* Expanded display mode? */
  5323.  SHORT       sLinesEach = 1;         /* 1 or LINES_PER_WINDOW */
  5324.  SHORT       sCharX;                 /* Character width in pixels */
  5325.  SHORT       sCharY;                 /* Character height in pixels */
  5326.  SHORT       sDescenderY;            /* Descender height */
  5327.  SHORT       sExtLeading;            /* Vert. space between chars */
  5328.  HPS         hpsPaint;               /* PS for SleuthPaint */
  5329.  POINTL      ptlPaint;               /* Current pos for SleuthPaint */
  5330.  CHAR        szClass[10];            /* Our window class name */
  5331.  CHAR        szTitle[40];            /* Our window title */
  5332.  
  5333.  /* Function prototypes. */
  5334.  
  5335.  VOID                main( VOID );
  5336.  PSZ                 SleuthClassName( PSZ );
  5337.  BOOL                SleuthGetAllWindows( VOID );
  5338.  VOID                SleuthGetWindowTree( HWND hwnd, SHORT sLevel );
  5339.  BOOL                SleuthInit( VOID );
  5340.  PSZ                 SleuthMsgName( USHORT );
  5341.  VOID                SleuthPaint( HWND hwndPaint );
  5342.  VOID      CDECL     SleuthPaint1( CHAR* szFormat, ... );
  5343.  VOID                SleuthQueryScrollBar( HWND hwndBar, SHORT* psPos,
  5344.                                            SHORT* psMin,
  5345.                                            SHORT* psMax );
  5346.  SHORT               SleuthScroll( HWND hwnd, USHORT msg,
  5347.                                    USHORT idBar,
  5348.                                    USHORT cmd, SHORT sPos );
  5349.  VOID                SleuthSetOneBar( HWND hwndBar, SHORT sMax );
  5350.  VOID                SleuthSetScrollBars( VOID );
  5351.  VOID                SleuthTerminate( VOID );
  5352.  MRESULT   EXPENTRY  SleuthWndProc( HWND, USHORT, MPARAM, MPARAM );
  5353.  
  5354.  /* Application main program. */
  5355.  
  5356.  VOID main()
  5357.  {
  5358.      QMSG        qmsg;
  5359.  
  5360.      /* Initialize application, quit if any errors */
  5361.  
  5362.      if( ! SleuthInit() )
  5363.        return;
  5364.  
  5365.      /* Main message processing loop */
  5366.  
  5367.      while( WinGetMsg( hab, &qmsg, NULL, 0, 0 ) )
  5368.        WinDispatchMsg( hab, &qmsg );
  5369.  
  5370.      /* Application termination */
  5371.  
  5372.      SleuthTerminate();
  5373.  }
  5374.  
  5375.  /* Convert a class name into its printable form.  Normal class     */
  5376.  /* names are returned unchanged; the special WC_ "names" are       */
  5377.  /* converted into text.                                            */
  5378.  
  5379.  typedef struct _CLASSNAMES {
  5380.      NPSZ        szNum;
  5381.      NPSZ        szName;
  5382.  } CLASSNAMES;
  5383.  
  5384.  typedef CLASSNAMES NEAR * NPCLASSNAMES;
  5385.  
  5386.  CLASSNAMES aClassNames[] = {
  5387.      { "#1",  "WC_FRAME"      },
  5388.      { "#2",  "WC_DIALOG"     },
  5389.      { "#3",  "WC_BUTTON"     },
  5390.      { "#4",  "WC_MENU"       },
  5391.      { "#5",  "WC_STATIC"     },
  5392.      { "#6",  "WC_ENTRYFIELD" },
  5393.      { "#7",  "WC_LISTBOX"    },
  5394.      { "#8",  "WC_SCROLLBAR"  },
  5395.      { "#9",  "WC_TITLEBAR"   },
  5396.      { "#10", "WC_SIZEBORDER" },
  5397.      { NULL,  NULL            }
  5398.  };
  5399.  
  5400.  PSZ SleuthClassName( pszClass )
  5401.      PSZ         pszClass;
  5402.  {
  5403.      NPCLASSNAMES npNames;
  5404.  
  5405.      if( pszClass[0] != '#' )
  5406.        return pszClass;
  5407.  
  5408.      for( npNames = &aClassNames[0];  npNames->szNum;  npNames++ )
  5409.        if( strcmp( pszClass, npNames->szNum ) == 0 )
  5410.          return npNames->szName;
  5411.  
  5412.      return pszClass;
  5413.  }
  5414.  
  5415.  /* Gather up information on all windows in PM and fill in the      */
  5416.  /* INFO structure for them.                                        */
  5417.  
  5418.  BOOL SleuthGetAllWindows()
  5419.  {
  5420.      sWinCount = 0;
  5421.  
  5422.      /* Pick up both trees, from hwndDesktop and hwndObject */
  5423.  
  5424.      SleuthGetWindowTree( hwndDesktop, 0 );
  5425.      SleuthGetWindowTree( hwndObject,  0 );
  5426.  
  5427.      /* Set scroll bars based on new window count */
  5428.  
  5429.      SleuthSetScrollBars();
  5430.  
  5431.      /* Force our window to be repainted */
  5432.  
  5433.      WinInvalidateRect( hwndSleuth, NULL, TRUE );
  5434.  
  5435.      return TRUE;
  5436.  }
  5437.  
  5438.  /* Gather information on all windows in the tree starting at hwnd, */
  5439.  /* and add an entry to the INFO array for each one.                */
  5440.  
  5441.  VOID SleuthGetWindowTree( hwnd, sLevel )
  5442.      HWND        hwnd;
  5443.      SHORT       sLevel;
  5444.  {
  5445.      PINFO       pInfo;
  5446.      HWND        hwndChild;
  5447.  
  5448.      /* Count the window and allocate an INFO entry */
  5449.  
  5450.      sWinCount++;
  5451.  
  5452.      if( ! pInfoBase )
  5453.        pInfoBase = malloc( sizeof(INFO) );
  5454.      else
  5455.        pInfoBase = realloc( pInfoBase, sWinCount*sizeof(INFO) );
  5456.  
  5457.      if( ! pInfoBase )
  5458.        exit( 9 );
  5459.  
  5460.      pInfo = pInfoBase + sWinCount - 1;  /* -> INFO for this window */
  5461.  
  5462.      /* Gather up this window's information */
  5463.  
  5464.      pInfo->hwnd = hwnd;
  5465.      pInfo->fSelect = FALSE;
  5466.      pInfo->fHasText = FALSE;
  5467.      pInfo->class.flClassStyle  = 0L;
  5468.      pInfo->class.pfnWindowProc = 0L;
  5469.      pInfo->class.cbWindowData  = 0;
  5470.  
  5471.      pInfo->flStyle = WinQueryWindowULong( hwnd, QWL_STYLE );
  5472.      pInfo->hwndOwner = WinQueryWindow( hwnd, QW_OWNER, FALSE );
  5473.      pInfo->pfnwp = WinQueryWindowPtr( hwnd, QWP_PFNWP );
  5474.      pInfo->usWindowID = WinQueryWindowUShort( hwnd, QWS_ID );
  5475.      WinQueryWindowProcess( hwnd, &pInfo->ProcessID,
  5476.                                   &pInfo->ThreadID );
  5477.      pInfo->hmq = WinQueryWindowPtr( hwnd, QWL_HMQ );
  5478.      WinQueryWindowRect( hwnd, &pInfo->rclWindow );
  5479.      pInfo->sLockCount = WinQueryWindowLockCount( hwnd );
  5480.      pInfo->sLevel = sLevel;
  5481.  
  5482.      if( hwnd == hwndDesktop )
  5483.        strcpy( pInfo->szClass, "WC_DESKTOP" );
  5484.      else if( hwnd == hwndObject )
  5485.        strcpy( pInfo->szClass, "WC_OBJECT" );
  5486.      else
  5487.      {
  5488.        WinQueryClassName( hwnd, sizeof(pInfo->szClass),
  5489.                           pInfo->szClass );
  5490.        WinQueryClassInfo( hab, pInfo->szClass, &pInfo->class );
  5491.        if( ! WinIsRectEmpty( hab, &pInfo->rclWindow ) )
  5492.          WinMapWindowRect( hwnd, WinQueryWindow(hwnd,QW_PARENT,FALSE),
  5493.                            &pInfo->rclWindow );
  5494.      }
  5495.  
  5496.      pInfo->szText[0] = '\0';
  5497.      if( pInfo->ProcessID == pidi.pid )
  5498.        pInfo->fHasText =  /* wrong... */
  5499.          !! WinQueryWindowText( hwnd, sizeof(pInfo->szText),
  5500.                                 pInfo->szText );
  5501.  /* Recurse through all child windows */
  5502.      for( hwndChild = WinQueryWindow( hwnd, QW_TOP, FALSE );
  5503.           hwndChild;
  5504.           hwndChild = WinQueryWindow( hwnd, QW_NEXT, FALSE ) )
  5505.         SleuthGetWindowTree( hwndChild, sLevel+1 );
  5506.  }
  5507.  
  5508.  /* Initialize the application. */
  5509.  
  5510.  BOOL SleuthInit()
  5511.  {
  5512.      HDC         hps;
  5513.      FONTMETRICS fm;
  5514.      SHORT       sScreenX;
  5515.      SHORT       sScreenY;
  5516.      ULONG       flFrameFlags;
  5517.  
  5518.      /* Pick up the basic information we need */
  5519.  
  5520.      DosGetPID( &pidi );
  5521.  
  5522.      hab = WinInitialize( 0 );
  5523.      hmq = WinCreateMsgQueue( hab, 0 );
  5524.  
  5525.      hwndDesktop = WinQueryDesktopWindow( hab, NULL );
  5526.      hwndObject  = WinQueryObjectWindow( hwndDesktop );
  5527.  
  5528.      sScreenX = (SHORT)WinQuerySysValue( hwndDesktop, SV_CXSCREEN );
  5529.      sScreenY = (SHORT)WinQuerySysValue( hwndDesktop, SV_CYSCREEN );
  5530.  
  5531.      /* Calculate character size for system font */
  5532.  
  5533.      hps = WinGetPS( hwndDesktop );
  5534.  
  5535.      GpiQueryFontMetrics( hps, (LONG)sizeof(fm), &fm );
  5536.  
  5537.      sCharX = (SHORT)fm.lAveCharWidth;
  5538.      sCharY = (SHORT)fm.lMaxBaselineExt;
  5539.      sDescenderY = (SHORT)fm.lMaxDescender;
  5540.  
  5541.      WinReleasePS( hps );
  5542.  
  5543.      WinLoadString( hab, NULL, IDS_CLASS, sizeof(szClass), szClass );
  5544.      WinLoadString( hab, NULL, IDS_TITLE, sizeof(szTitle), szTitle );
  5545.  
  5546.      WinRegisterClass( hab, szClass, SleuthWndProc, 0L, 0 );
  5547.  
  5548.          flFrameFlags = FCF_SIZEBORDER | FCF_TITLEBAR |
  5549.                          FCF_MINMAX | FCF_SYSMENU | FCF_MENU | FCF_ICON |
  5550.                          FCF_VERTSCROLL | FCF_HORZSCROLL |
  5551.                          FCF_SHELLPOSITION;
  5552.          hwndFrame =
  5553.          WinCreateStdWindow (hwndDesktop, WS_VISIBLE, &flFrameFlags,
  5554.                                  szClass, szTitle, 0L, NULL,
  5555.                                  ID_SLEUTH, &hwndSleuth) ;
  5556.  
  5557.      hwndHorzScroll = WinWindowFromID( hwndFrame, FID_HORZSCROLL );
  5558.      hwndVertScroll = WinWindowFromID( hwndFrame, FID_VERTSCROLL );
  5559.  
  5560.      WinSetWindowPos( hwndFrame, NULL,
  5561.                       sScreenX *  1 / 20,     /* X: 5% from left */
  5562.                       sScreenY *  2 / 10,     /* Y  20% from bottom */
  5563.                       sScreenX *  9 / 10,     /* nWidth: 90% */
  5564.                       sScreenY *  7 / 10,     /* nHeight: 70% */
  5565.                       SWP_MOVE | SWP_SIZE );
  5566.  
  5567.      /* Make our window visible now, so it's included in the list */
  5568.  
  5569.      WinShowWindow( hwndFrame, TRUE );
  5570.  
  5571.      /* Post a message to ourself to trigger the first  display */
  5572.  
  5573.      WinPostMsg( hwndSleuth, WM_COMMAND, MPFROMSHORT(CMD_LOOK), 0L );
  5574.  
  5575.      return TRUE;
  5576.  }
  5577.  
  5578.  /* Paint our window. */
  5579.  
  5580.  VOID SleuthPaint( hwnd )
  5581.      HWND        hwnd;
  5582.  {
  5583.      SHORT       sWin;
  5584.      SHORT       X;
  5585.      SHORT       sScrollY, sScrollX;
  5586.      RECTL       rclPaint, rclClient;
  5587.      PINFO       pInfo;
  5588.      CHAR        szQuote[2];
  5589.  
  5590.      /* Get the PS and erase it */
  5591.  
  5592.      hpsPaint = WinBeginPaint( hwnd, NULL, &rclPaint );
  5593.  
  5594.      GpiErase( hpsPaint );
  5595.  
  5596.      /* Find out how big the window is and how it's scrolled */
  5597.  
  5598.      WinQueryWindowRect( hwnd, &rclClient );
  5599.      sScrollX =
  5600.        (SHORT)WinSendMsg( hwndHorzScroll, SBM_QUERYPOS, 0, 0 );
  5601.      sScrollY =
  5602.        (SHORT)WinSendMsg( hwndVertScroll, SBM_QUERYPOS, 0, 0 );
  5603.  
  5604.      /* Calculate horizontal paint pos from scroll bar pos */
  5605.  
  5606.      X = /* ( 1 */ - sScrollX /* ) */ * sCharX;
  5607.  
  5608.      /* Calculate index into INFO array and vertical paint pos,
  5609.         from scroll bar pos and top of painting rectangle */
  5610.  
  5611.      sWin =
  5612.        ( ( (SHORT)rclClient.yTop - (SHORT)rclPaint.yTop ) / sCharY
  5613.          + sScrollY )
  5614.        / sLinesEach;
  5615.  
  5616.      ptlPaint.y =
  5617.        (SHORT)rclClient.yTop + sDescenderY
  5618.          - ( sWin * sLinesEach - sScrollY + 1 ) * sCharY;
  5619.  
  5620.      pInfo = pInfoBase + sWin;
  5621.  
  5622.      /* Loop through and paint each entry */
  5623.  
  5624.      while( sWin < sWinCount  &&
  5625.             (SHORT)ptlPaint.y + sCharY >= (SHORT)rclPaint.yBottom )
  5626.      {
  5627.        /* Set X position and indent child windows */
  5628.  
  5629.        ptlPaint.x = X + pInfo->sLevel * sCharX * (fExpand ? 4 : 2);
  5630.  
  5631.        szQuote[0] = szQuote[1] = '\0';
  5632.        if( pInfo->fHasText )
  5633.          szQuote[0] = '"';
  5634.  
  5635.        if( ! fExpand )
  5636.        {
  5637.          /* Paint the one-liner */
  5638.  
  5639.          SleuthPaint1(
  5640.            "%08lX [%04X] {%s} (%d,%d;%d,%d) %s%s%s",
  5641.            pInfo->hwnd,
  5642.            pInfo->usWindowID,
  5643.            SleuthClassName( pInfo->szClass ),
  5644.            (INT)pInfo->rclWindow.xLeft,
  5645.            (INT)pInfo->rclWindow.yBottom,
  5646.            (INT)pInfo->rclWindow.xRight,
  5647.            (INT)pInfo->rclWindow.yTop,
  5648.            szQuote, pInfo->szText, szQuote
  5649.          );
  5650.        }
  5651.  
  5652.        /* Increment to next INFO entry */
  5653.        sWin++;
  5654.        pInfo++;
  5655.      }
  5656.  
  5657.      WinEndPaint( hpsPaint );
  5658.  }
  5659.  
  5660.  /* Paint one line of text, using the global variables hpsPaint and */
  5661.  /* ptlPaint.  The #ifdef PM_MACINTOSH is because Lightspeed C      */
  5662.  /* doesn't like the ... notation, and Microsoft C doesn't like to  */
  5663.  /* do without it!                                                  */
  5664.  
  5665.  #ifdef PM_MACINTOSH
  5666.  VOID CDECL SleuthPaint1( szFormat )
  5667.  #else
  5668.  VOID CDECL SleuthPaint1( szFormat, ... )
  5669.  #endif
  5670.      CHAR *      szFormat;
  5671.  {
  5672.      va_list     pArgs;
  5673.      CHAR        szBuf[160];
  5674.  
  5675.      va_start( pArgs, szFormat );
  5676.  
  5677.      GpiCharStringAt(
  5678.        hpsPaint, &ptlPaint,
  5679.        (LONG)vsprintf( szBuf, szFormat, pArgs ),
  5680.        szBuf
  5681.      );
  5682.  
  5683.      va_end( pArgs );
  5684.  
  5685.      ptlPaint.y -= sCharY;
  5686.  }
  5687.  
  5688.  /* Get a scroll bar's range and position.  More convenient than    */
  5689.  /* sending the messages every time.                                */
  5690.  
  5691.  VOID SleuthQueryScrollBar( hwndBar, psPos, psMin, psMax )
  5692.      HWND        hwndBar;
  5693.      SHORT*      psPos;
  5694.      SHORT*      psMin;
  5695.      SHORT*      psMax;
  5696.  {
  5697.      MRESULT     mrRange;
  5698.  
  5699.      *psPos  = (SHORT)WinSendMsg( hwndBar, SBM_QUERYPOS, 0, 0 );
  5700.  
  5701.      mrRange = WinSendMsg( hwndBar, SBM_QUERYRANGE, 0, 0 );
  5702.      *psMin = SHORT1FROMMR(mrRange);
  5703.      *psMax = SHORT2FROMMR(mrRange);
  5704.  }
  5705.  
  5706.  /* Scroll hwnd and adjust idBar according to cmd and sPos. */
  5707.  
  5708.  SHORT SleuthScroll( hwnd, msg, idBar, cmd, sPos )
  5709.      HWND        hwnd;
  5710.      USHORT      msg;
  5711.      USHORT      idBar;
  5712.      USHORT      cmd;
  5713.      SHORT       sPos;
  5714.  {
  5715.      HWND        hwndBar;
  5716.      SHORT       sOldPos;
  5717.      SHORT       sDiff;
  5718.      SHORT       sMin;
  5719.      SHORT       sMax;
  5720.      SHORT       sPageSize;
  5721.      RECTL       rcl;
  5722.  
  5723.      /* Get old scroll position and scroll range */
  5724.  
  5725.      hwndBar =
  5726.        WinWindowFromID( WinQueryWindow(hwnd,QW_PARENT,FALSE), idBar );
  5727.  
  5728.      SleuthQueryScrollBar( hwndBar, &sOldPos, &sMin, &sMax );
  5729.  
  5730.      /* Calculate page size, horizontal or vertical as needed */
  5731.  
  5732.      WinQueryWindowRect( hwnd, &rcl );
  5733.  
  5734.      if( msg == WM_HSCROLL )
  5735.        sPageSize = ( (SHORT)rcl.xRight - (SHORT)rcl.xLeft) / sCharX;
  5736.      else
  5737.        sPageSize = ( (SHORT)rcl.yTop - (SHORT)rcl.yBottom) / sCharY;
  5738.  
  5739.      /* Select the amount to scroll by, based on the scroll message */
  5740.  
  5741.      switch( cmd )
  5742.      {
  5743.        case SB_LINEUP:
  5744.          sDiff = -1;
  5745.          break;
  5746.  
  5747.        case SB_LINEDOWN:
  5748.          sDiff = 1;
  5749.          break;
  5750.  
  5751.        case SB_PAGEUP:
  5752.          sDiff = -sPageSize;
  5753.          break;
  5754.  
  5755.        case SB_PAGEDOWN:
  5756.          sDiff = sPageSize;
  5757.          break;
  5758.  
  5759.        case SB_SLIDERPOSITION:
  5760.          sDiff = sPos - sOldPos;
  5761.          break;
  5762.  
  5763.        case SBX_TOP:
  5764.          sDiff = -30000;  /* Kind of a kludge but it works... */
  5765.          break;
  5766.  
  5767.        case SBX_BOTTOM:
  5768.          sDiff = 30000;
  5769.          break;
  5770.  
  5771.        default:
  5772.          return 0;
  5773.      }
  5774.  
  5775.      /* Limit scroll destination to sMin..sMax */
  5776.  
  5777.      if( sDiff < sMin - sOldPos )
  5778.        sDiff = sMin - sOldPos;
  5779.  
  5780.      if( sDiff > sMax - sOldPos )
  5781.        sDiff = sMax - sOldPos;
  5782.  
  5783.      /* Return if net effect is nothing */
  5784.  
  5785.      if( sDiff == 0 )
  5786.        return 0;
  5787.  
  5788.      /* Set the new scroll bar position and scroll the window */
  5789.  
  5790.      WinSendMsg( hwndBar, SBM_SETPOS, MRFROMSHORT(sOldPos+sDiff), 0 );
  5791.  
  5792.      WinScrollWindow(
  5793.        hwnd,
  5794.        msg == WM_HSCROLL ?  -sDiff*sCharX : 0,
  5795.        msg == WM_VSCROLL ?   sDiff*sCharY : 0,
  5796.        NULL, NULL, NULL, NULL,
  5797.        SW_INVALIDATERGN
  5798.      );
  5799.  
  5800.      /* Force an immediate update for cleaner appearance */
  5801.  
  5802.      WinUpdateWindow( hwnd );
  5803.  
  5804.      return sDiff;
  5805.  }
  5806.  
  5807.  /* Set one scroll bar's position and range. */
  5808.  
  5809.  VOID SleuthSetOneBar( hwndBar, sMax )
  5810.      HWND        hwndBar;
  5811.      SHORT       sMax;
  5812.  {
  5813.      SHORT       sPos, sOldMin, sOldMax;
  5814.  
  5815.      SleuthQueryScrollBar( hwndBar, &sPos, &sOldMin, &sOldMax );
  5816.  
  5817.      if( sMax <= 0 )
  5818.        sMax = sPos = 0;
  5819.  
  5820.      if( sMax != sOldMax )
  5821.      {
  5822.        WinSendMsg( hwndBar, SBM_SETSCROLLBAR,
  5823.                    MPFROMSHORT(sPos), MPFROM2SHORT(0,sMax) );
  5824.  
  5825.        WinEnableWindow( hwndBar, !!(sMax) );
  5826.      }
  5827.  }
  5828.  
  5829.  /* Set both scroll bars according to the window size and the       */
  5830.  /* number of INFO entries.                                         */
  5831.  
  5832.  VOID SleuthSetScrollBars()
  5833.  {
  5834.      RECTL       rcl;
  5835.  
  5836.      WinQueryWindowRect( hwndSleuth, &rcl );
  5837.  
  5838.      SleuthSetOneBar( hwndHorzScroll,
  5839.                       WINDOW_WIDTH - (SHORT)rcl.xRight / sCharX );
  5840.  
  5841.      SleuthSetOneBar( hwndVertScroll,
  5842.                       sWinCount*sLinesEach - (SHORT)rcl.yTop/sCharY );
  5843.  }
  5844.  
  5845.  /* Terminate the application. */
  5846.  
  5847.  VOID SleuthTerminate()
  5848.  {
  5849.      WinDestroyWindow( hwndFrame );
  5850.      WinDestroyMsgQueue( hmq );
  5851.      WinTerminate( hab );
  5852.  
  5853.      exit( 0 );
  5854.  }
  5855.  
  5856.  /* Window function for Sleuth's main window. */
  5857.  
  5858.  MRESULT EXPENTRY SleuthWndProc( hwnd, msg, mp1, mp2 )
  5859.      HWND          hwnd;
  5860.      USHORT        msg;
  5861.      MPARAM        mp1;
  5862.      MPARAM        mp2;
  5863.  {
  5864.      switch( msg )
  5865.      {
  5866.        /* Tell PM that we don't need no stinkin' upside down coordinates! */
  5867.  
  5868.        case WM_CALCVALIDRECTS:
  5869.          return MRFROMSHORT( CVR_ALIGNLEFT | CVR_ALIGNTOP );
  5870.  
  5871.        /* Menu command message - process the command */
  5872.  
  5873.        case WM_COMMAND:
  5874.          switch( COMMANDMSG(&msg)->cmd )
  5875.          {
  5876.            case CMD_ABOUT:
  5877.              return 0L;
  5878.  
  5879.            case CMD_EXIT:
  5880.              WinPostMsg( hwnd, WM_QUIT, 0L, 0L );
  5881.              return 0L;
  5882.  
  5883.            case CMD_LOOK:
  5884.              SleuthGetAllWindows();
  5885.              return 0L;
  5886.          }
  5887.          return 0L;
  5888.  
  5889.          /* Scroll messages - scroll the window */
  5890.  
  5891.          case WM_HSCROLL:
  5892.          case WM_VSCROLL:
  5893.            SleuthScroll( hwnd, msg, SHORT1FROMMP(mp1),
  5894.                          SHORT2FROMMP(mp2), SHORT1FROMMP(mp2) );
  5895.            return 0L;
  5896.  
  5897.          /* Key-down message - handle cursor keys, ignore others */
  5898.  
  5899.          case WM_CHAR:
  5900.            switch( CHARMSG(&msg)->vkey )
  5901.            {
  5902.              case VK_LEFT:
  5903.              case VK_RIGHT:
  5904.                return WinSendMsg( hwndHorzScroll, msg, mp1, mp2 );
  5905.              case VK_UP:
  5906.              case VK_DOWN:
  5907.              case VK_PAGEUP:
  5908.              case VK_PAGEDOWN:
  5909.                return WinSendMsg( hwndVertScroll, msg, mp1, mp2 );
  5910.            }
  5911.            return 0L;
  5912.  
  5913.          /* Paint message - repaint all or part of our window */
  5914.  
  5915.          case WM_PAINT:
  5916.            SleuthPaint( hwnd );
  5917.            return 0L;
  5918.  
  5919.          /* Size message - recalculate our scroll bars */
  5920.  
  5921.          case WM_SIZE:
  5922.            SleuthSetScrollBars();
  5923.            return 0L;
  5924.      }
  5925.  
  5926.      /* All other messages go to DefWindowProc */
  5927.  
  5928.      return WinDefWindowProc( hwnd, msg, mp1, mp2 );
  5929.  }
  5930.  
  5931.  
  5932.  Figure 6:  MacPM WinInitialize
  5933.  
  5934.  HAB APIENTRY WinInitialize( fOptions )
  5935.      USHORT      fOptions;
  5936.  {
  5937.      /* Static initialization - less hassle this way? */
  5938.  
  5939.      _pps1 = &_ps1;
  5940.      _hps1 = (HPS)&_pps1;
  5941.  
  5942.      /* Macintosh memory management initialization */
  5943.  
  5944.      MaxApplZone();
  5945.      MoreMasters();
  5946.      MoreMasters();
  5947.      MoreMasters();
  5948.  
  5949.      /* Open resource file - TEMP */
  5950.  
  5951.      OpenResFile( "\pMacPM.rsrc" );
  5952.  
  5953.      return (HAB)1;
  5954.  }
  5955.  
  5956.  
  5957.  Figure 7:  MacPM WinCreateMsgQueue
  5958.  
  5959.  HMQ APIENTRY WinCreateMsgQueue( hab, sSize )
  5960.      HAB         hab;
  5961.      SHORT       sSize;
  5962.  {
  5963.      PMYWND      pwnd;
  5964.  
  5965.    /* Generic Macintosh initialization (already did memory stuff) */
  5966.  
  5967.      InitGraf( &thePort );
  5968.      InitFonts();
  5969.      FlushEvents( everyEvent, 0 );
  5970.      SetEventMask( everyEvent );
  5971.      InitWindows();
  5972.      InitMenus();
  5973.      TEInit();
  5974.      InitDialogs( 0L );
  5975.      InitCursor();
  5976.  
  5977.    /* Initialize the SV_ values and register the predefined window classes */
  5978.  
  5979.      MpmInitSysValues();
  5980.  
  5981.      if( ! WinRegisterClass( hab, WC_BUTTON, MpmFnwpButton,
  5982.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  5983.        return NULL;
  5984.  
  5985.      if( ! WinRegisterClass( hab, WC_DESKTOP, MpmFnwpDesktop,
  5986.                              CS_PUBLIC, 0 ) )
  5987.        return NULL;
  5988.  
  5989.  #ifdef FUTURE
  5990.      if( ! WinRegisterClass( hab, WC_DIALOG, MpmFnwpDialog,
  5991.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  5992.        return NULL;
  5993.  #endif
  5994.  
  5995.      if( ! WinRegisterClass( hab, WC_ENTRYFIELD, MpmFnwpEntryField,
  5996.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  5997.        return NULL;
  5998.  
  5999.      if( ! WinRegisterClass( hab, WC_FRAME, MpmFnwpFrame,
  6000.                              CS_MOVENOTIFY | CS_PUBLIC, 0x20 /*??*/ ) )
  6001.        return NULL;
  6002.  
  6003.      if( ! WinRegisterClass( hab, WC_LISTBOX, MpmFnwpListBox,
  6004.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6005.        return NULL;
  6006.  
  6007.      if( ! WinRegisterClass( hab, WC_MENU, MpmFnwpMenu,
  6008.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6009.        return NULL;
  6010.  
  6011.      if( ! WinRegisterClass( hab, WC_SCROLLBAR, MpmFnwpScrollBar,
  6012.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6013.        return NULL;
  6014.  
  6015.      if( ! WinRegisterClass( hab, WC_SIZEBORDER, MpmFnwpSizeBorder,
  6016.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6017.        return NULL;
  6018.  
  6019.      if( ! WinRegisterClass( hab, WC_STATIC, MpmFnwpStatic,
  6020.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6021.        return NULL;
  6022.  
  6023.      if( ! WinRegisterClass( hab, WC_TITLEBAR, MpmFnwpTitleBar,
  6024.                              CS_MOVENOTIFY | CS_PUBLIC, 0 ) )
  6025.        return NULL;
  6026.  
  6027.      /* Create the object and desktop windows */
  6028.  
  6029.      _hwndObject =
  6030.        WinCreateWindow(
  6031.          NULL, WC_DESKTOP, _szNull,
  6032.          0, 0, 0, 0, 0,
  6033.          NULL, NULL, 0, NULL, NULL
  6034.        );
  6035.      if( ! _hwndObject )
  6036.        return NULL;
  6037.  
  6038.      pwnd = PMYWNDOF(_hwndObject);
  6039.      pwnd->ucKind = WK_OBJECT;
  6040.      pwnd->pfnwp  = MpmFnwpObject;
  6041.  
  6042.      _hwndDesktop =
  6043.        WinCreateWindow(
  6044.          NULL, WC_DESKTOP, _szNull,
  6045.          WS_DISABLED,
  6046.          0, 0, 0, 0,
  6047.          NULL, NULL, 0, NULL, NULL
  6048.        );
  6049.      if( ! _hwndDesktop )
  6050.        return NULL;
  6051.  
  6052.      pwnd = PMYWNDOF(_hwndDesktop);
  6053.      pwnd->cx = _alSysVal[SV_CXSCREEN];
  6054.      pwnd->cy = _alSysVal[SV_CYSCREEN];
  6055.      pwnd->flStyle |= WS_VISIBLE;
  6056.  
  6057.      return (HMQ)1;
  6058.  }
  6059.  
  6060.  
  6061.  Figure 8:  MacPM WinCreateWindow
  6062.  
  6063.  HWND APIENTRY
  6064.  WinCreateWindow( hwndParent, pszClass,  pszName, flStyle,
  6065.                   x, y, cx, cy, hwndOwner, hwndPrevSibling, id,
  6066.                   pCtlData, pPresParams )
  6067.      HWND        hwndParent, hwndOwner, hwndPrevSibling;
  6068.      PSZ         pszClass, pszName;
  6069.      ULONG       flStyle;
  6070.      SHORT       x, y, cx, cy;
  6071.      USHORT      id;
  6072.      PVOID       pCtlData, pPresParams;
  6073.  {
  6074.      HWND        hwnd, hwndNextSibling, hwndMac;
  6075.      PMYWND      pwnd;
  6076.      HMYCLASS    hClass;
  6077.      PMYCLASS    pClass;
  6078.      USHORT      usLen;
  6079.      SHORT       sProcID;
  6080.      Rect        rect, rectAdj;
  6081.      WindowPeek  pwin, pwinBehind;
  6082.      CHAR        szTitle[256];
  6083.      BOOL        fGoAway, fFrameWindow;
  6084.      UCHAR       ucKind;
  6085.      USHORT      usHash;
  6086.      CHAR        szClassBuf[10];
  6087.      ULONG       flFrameFlags;
  6088.  
  6089.      /* Figure out which WK_ code to use */
  6090.  
  6091.      if( pszClass == WC_DESKTOP )
  6092.        ucKind = WK_DESKTOP;
  6093.      else
  6094.      {
  6095.        if( ! hwndParent  ||  hwndParent == HWND_DESKTOP )
  6096.          hwndParent = _hwndDesktop;
  6097.        if( hwndParent == _hwndDesktop )
  6098.          ucKind = WK_MAIN;
  6099.        else
  6100.          ucKind = WK_CHILD;
  6101.      }
  6102.  
  6103.      /* Fix up WC_ names, calculate hash, and find window class */
  6104.  
  6105.      MpmFixName( &pszClass, szClassBuf, &usHash );
  6106.  
  6107.      hClass = MpmFindClass( pszClass, usHash );
  6108.      if( ! hClass )
  6109.        return NULL;
  6110.  
  6111.      /* Grab frame flags if it's the window frame class */
  6112.  
  6113.      fFrameWindow = MpmIsFrameClass( pszClass );
  6114.      flFrameFlags =
  6115.        ( fFrameWindow && pCtlData ? *(PULONG)pCtlData : 0 );
  6116.  
  6117.      /* Allocate window structure */
  6118.  
  6119.      usLen = sizeof(MYWND) + (**hClass).class.cbWindowData;
  6120.      hwnd = (HWND)MpmNewHandleZ( usLen );
  6121.      if( ! hwnd )
  6122.        return NULL;
  6123.  
  6124.      /* Handle WK_ specific stuff */
  6125.  
  6126.      switch( ucKind )
  6127.      {
  6128.        /* Desktop window: just set window position */
  6129.  
  6130.        case WK_DESKTOP:
  6131.          SetRect( &rect, 0, 0, cx, cy );
  6132.          SetRect( &rectAdj, 0, 0, 0, 0 );
  6133.          break;
  6134.  
  6135.        /* Main Mac window: figure out which style of Mac window to use and
  6136.           create it */
  6137.  
  6138.        case WK_MAIN:
  6139.          fGoAway = FALSE;
  6140.          if( ! ( flStyle & FS_BORDER )  &&
  6141.              ! ( flFrameFlags & FCF_DOCBITS ) )
  6142.          {
  6143.            sProcID = plainDBox;  /* shouldn't have border! */
  6144.            rectAdj = _rectAdjPlainDBox;
  6145.          }
  6146.          else if( flFrameFlags & FCF_DOCBITS )
  6147.          {
  6148.            sProcID = documentProc;
  6149.            rectAdj = _rectAdjDocumentProc;
  6150.            if( flFrameFlags & FCF_MAXBUTTON )
  6151.              sProcID += 8;
  6152.            if( flFrameFlags & FCF_SYSMENU )
  6153.              fGoAway = TRUE;
  6154.          }
  6155.          else if( flStyle & FS_DLGBORDER )
  6156.          {
  6157.            sProcID = dBoxProc;
  6158.            rectAdj = _rectAdjDBoxProc;
  6159.          }
  6160.          else
  6161.          {
  6162.            sProcID = altDBoxProc;
  6163.            rectAdj = _rectAdjAltDBoxProc;
  6164.          }
  6165.  
  6166.          rect.left   = rectAdj.left   + x;
  6167.          rect.right  = rectAdj.right  + x + cx;
  6168.          rect.top    = rectAdj.top    + rect.bottom - cy;
  6169.          rect.bottom = rectAdj.bottom + screenBits.bounds.bottom - y;
  6170.  
  6171.          /* Should check rect for out of screen boundaries! */
  6172.  
  6173.          strncpy( szTitle, pszName, 255 );
  6174.          szTitle[255] = '\0';
  6175.  
  6176.          if( hwndPrevSibling == HWND_TOP )
  6177.            pwinBehind = (WindowPeek)(-1);
  6178.          else if( hwndPrevSibling == HWND_BOTTOM )
  6179.            pwinBehind = (WindowPeek)NULL;
  6180.          else if( ISMAINWINDOW(hwndPrevSibling) )
  6181.            pwinBehind = PWINOFHWND(hwndPrevSibling);
  6182.          else
  6183.            ERROR( "WinCreateWindow: Invalid hwndPrevSibling" );
  6184.  
  6185.          pwin =
  6186.            (WindowPeek)NewWindow( NULL, &rect, CtoPstr(szTitle),
  6187.                                   FALSE, sProcID, pwinBehind,
  6188.                                   fGoAway, (LONG)hwnd );
  6189.          if( ! pwin )
  6190.          {
  6191.            DisposHandle( (Handle)hwnd );
  6192.            return NULL;
  6193.          }
  6194.          PWINOFHWND(hwnd) = pwin;
  6195.  
  6196.          break;
  6197.  
  6198.        /* Child window: set up all the "kin" windows */
  6199.  
  6200.        case WK_CHILD:
  6201.          if( hwndPrevSibling == HWND_TOP )
  6202.          {
  6203.            MYWNDOF(hwnd).hwndNextSibling = hwndNextSibling =
  6204.              MYWNDOF(hwndParent).hwndTopChild;
  6205.            MYWNDOF(hwndNextSibling).hwndPrevSibling = hwnd;
  6206.            MYWNDOF(hwndParent).hwndTopChild = hwnd;
  6207.          }
  6208.          else if( hwndPrevSibling == HWND_BOTTOM )
  6209.          {
  6210.            MYWNDOF(hwnd).hwndPrevSibling = hwndPrevSibling =
  6211.              MYWNDOF(hwndParent).hwndBottomChild;
  6212.            MYWNDOF(hwndPrevSibling).hwndNextSibling = hwnd;
  6213.            MYWNDOF(hwndParent).hwndBottomChild = hwnd;
  6214.          }
  6215.          else
  6216.          {
  6217.            if( ! MpmValidateWindow(hwndPrevSibling) )
  6218.              return NULL;
  6219.            if( MYWNDOF(hwndPrevSibling).hwndParent != hwndParent )
  6220.              return NULL;
  6221.            MYWNDOF(hwnd).hwndNextSibling = hwndNextSibling =
  6222.              MYWNDOF(hwndPrevSibling).hwndNextSibling;
  6223.            MYWNDOF(hwnd).hwndPrevSibling = hwndPrevSibling;
  6224.            MYWNDOF(hwndPrevSibling).hwndNextSibling = hwnd;
  6225.            if( hwndNextSibling )
  6226.              MYWNDOF(hwndNextSibling).hwndPrevSibling = hwnd;
  6227.            else
  6228.              MYWNDOF(hwndParent).hwndBottomChild = hwnd;
  6229.          }
  6230.          if( ! MYWNDOF(hwndParent).hwndTopChild )
  6231.            MYWNDOF(hwndParent).hwndTopChild = hwnd;
  6232.          if( ! MYWNDOF(hwndParent).hwndBottomChild )
  6233.            MYWNDOF(hwndParent).hwndBottomChild = hwnd;
  6234.          for( hwndMac = hwndParent;
  6235.               ISCHILDWINDOW(hwndMac);
  6236.               hwndMac = MYWNDOF(hwndMac).hwndParent );
  6237.          PWINOFHWND(hwnd) = PWINOFHWND(hwndMac);
  6238.          rectAdj = MYWNDOF(hwndMac).rectAdj;
  6239.          break;
  6240.      }
  6241.  
  6242.      /* Fill in the window structure fields */
  6243.  
  6244.      pClass = *hClass;
  6245.      pwnd = PMYWNDOF(hwnd);
  6246.  
  6247.      pwnd->signature = WND_SIGNATURE;
  6248.      pwnd->ucKind = ucKind;
  6249.      pwnd->pfnwp = pClass->class.pfnWindowProc;
  6250.      pwnd->hclass = hClass;
  6251.      pwnd->flStyle =
  6252.        ( flStyle & ~WS_VISIBLE ) |
  6253.        ( pClass->class.flClassStyle & CLASSWINDOWBITS );
  6254.      pwnd->hwndOwner = hwndOwner;
  6255.      pwnd->hwndParent = hwndParent;
  6256.      pwnd->id = id;
  6257.      pwnd->rectAdj = rectAdj;
  6258.      pwnd->flFrameFlags = flFrameFlags;
  6259.  
  6260.      /* Now the window is here for real, so send the WM_CREATE */
  6261.  
  6262.      if( WinSendMsg( hwnd, WM_CREATE,
  6263.                      MPFROMP(pCtlData), MPFROMP(&hwndParent) ) )
  6264.      {
  6265.        WinDestroyWindow( hwnd );
  6266.        return NULL;
  6267.      }
  6268.  
  6269.      /* Send the WM_ADJUSTWINDOWPOS if it's not the desktop window
  6270.         and it has a nonzero size */
  6271.  
  6272.      if( cx  &&  cy  &&  ucKind != WK_DESKTOP )
  6273.        WinSetWindowPos( hwnd, NULL, x, y, cx, cy, SWP_MOVE | SWP_SIZE );
  6274.  
  6275.      /* Make the window visible if it's supposed to be visible */
  6276.  
  6277.      if( flStyle & WS_VISIBLE )
  6278.        WinShowWindow( hwnd, TRUE );
  6279.  
  6280.      return hwnd;
  6281.  }
  6282.  
  6283.  
  6284.  Figure 9:  MacPM WinCreateStdWindow
  6285.  
  6286.  HWND APIENTRY WinCreateStdWindow( hwndParent, flStyle, pCtlData,
  6287.                                    pszClassClient, pszTitle,
  6288.                                    flStyleClient, hmod, idResources,
  6289.                                    phwndClient )
  6290.      HWND        hwndParent;
  6291.      ULONG       flStyle, flStyleClient;
  6292.      PVOID       pCtlData;
  6293.      PSZ         pszClassClient, pszTitle;
  6294.      HMODULE     hmod;
  6295.      USHORT      idResources;
  6296.      PHWND       phwndClient;
  6297.  {
  6298.      HWND        hwndFrame, hwndClient;
  6299.      SHORT       x, y, cx, cy;
  6300.      ULONG       flFrameFlags;
  6301.  
  6302.      /* Pick up frame flags, take care of special hwndParent values */
  6303.  
  6304.      flFrameFlags = ( pCtlData ? *(PULONG)pCtlData : 0 );
  6305.  
  6306.      if( ! hwndParent || hwndParent == HWND_DESKTOP )
  6307.        hwndParent = _hwndDesktop;
  6308.  
  6309.      ASSERT( hwndParent == _hwndDesktop,
  6310.              "WinCreateStdWindow: Must be a top level window" );
  6311.      ASSERT( ! hmod,
  6312.              "WinCreateStdWindow: hmod must be NULL" );
  6313.      ASSERT( ! (flStyle & WS_CLIPCHILDREN),
  6314.              "WinCreateStdWindow: WS_CLIPCHILDREN not allowed" );
  6315.  
  6316.      /* Assign default position if visible */
  6317.  
  6318.      if( flStyle & WS_VISIBLE )
  6319.      {
  6320.        x = 10;       /* TEMP HACK */
  6321.        y = screenBits.bounds.bottom - 200;
  6322.        cx = 250;
  6323.        cy = 150;
  6324.      }
  6325.      else
  6326.        x = y = cx = cy = 0;
  6327.  
  6328.      /* Create the frame window itself */
  6329.  
  6330.      hwndFrame =
  6331.        WinCreateWindow( hwndParent, WC_FRAME, _szNull,
  6332.                         flStyle & ~WS_VISIBLE, x, y, cx, cy,
  6333.                         NULL, HWND_TOP, idResources, pCtlData, NULL );
  6334.  
  6335.      if( ! hwndFrame )
  6336.        return NULL;
  6337.  
  6338.      /* Create frame controls according to flFrameFlags */
  6339.  
  6340.      MpmFrameUpdate( hwndFrame, flFrameFlags );
  6341.  
  6342.      /* Create client window if requested */
  6343.  
  6344.      if( pszClassClient && *pszClassClient )
  6345.      {
  6346.        *phwndClient = hwndClient =
  6347.          WinCreateWindow(
  6348.            hwndFrame, pszClassClient, _szNull, flStyleClient,
  6349.            0, 0, 0, 0, hwndFrame, HWND_BOTTOM, FID_CLIENT, NULL, NULL
  6350.          );
  6351.        if( ! hwndClient )
  6352.          goto exitDestroy;
  6353.      }
  6354.  
  6355.      /* Create menu and initialize title */
  6356.  
  6357.      if( flFrameFlags & FCF_MENU )
  6358.        if( ! MpmMenuLoad( hwndFrame, idResources ) )
  6359.          goto exitDestroy;
  6360.  
  6361.      if( flFrameFlags & FCF_TITLEBAR )
  6362.        WinSetWindowText( hwndFrame, pszTitle );
  6363.  
  6364.      /* Make window visible if requested */
  6365.  
  6366.      if( flStyle & WS_VISIBLE )
  6367.      {
  6368.        WinSendMsg( hwndFrame, WM_FORMATFRAME, 0L, 0L );
  6369.        WinShowWindow( hwndFrame, TRUE );
  6370.      }
  6371.  
  6372.      return hwndFrame;
  6373.  
  6374.  exitDestroy:
  6375.      if( pszClassClient && *pszClassClient )
  6376.        *phwndClient = NULL;
  6377.      WinDestroyWindow( hwndFrame );
  6378.      return NULL;
  6379.  }
  6380.  
  6381.  
  6382.  Figure 10:  MacPM Windowfunction for Scroll Bar Controls
  6383.  
  6384.  MRESULT EXPENTRY MpmFnwpScrollBar( hwnd, msg, mp1, mp2 )
  6385.      HWND        hwnd;
  6386.      USHORT      msg;
  6387.      MPARAM      mp1;
  6388.      MPARAM      mp2;
  6389.  {
  6390.      USHORT      id;
  6391.      ControlHandle hctl, hctl1;
  6392.      Rect        rect;
  6393.      Rect*       prect;
  6394.      SHORT       sPart;
  6395.      POINTL      ptl;
  6396.      SHORT       cmd;
  6397.      Handle      hcdef;
  6398.      LONG        lPoint;
  6399.      Point       point;
  6400.      static BOOL fTrackThumb = FALSE;
  6401.      static SHORT sInitValue;
  6402.  
  6403.      if( ! MpmValidateWindow(hwnd) )
  6404.        return FALSE;
  6405.  
  6406.      id   = MYWNDOF(hwnd).id;
  6407.      hctl = MYWNDOF(hwnd).hctl;
  6408.  
  6409.      switch( msg )
  6410.      {
  6411.        /* Return the current position */
  6412.  
  6413.        case SBM_QUERYPOS:
  6414.          return MRFROMSHORT( fTrackThumb ? sInitValue
  6415.                                          : (**hctl).contrlValue );
  6416.  
  6417.        /* Return the scroll bar range */
  6418.  
  6419.        case SBM_QUERYRANGE:
  6420.          return MRFROM2SHORT( (**hctl).contrlMin, (**hctl).contrlMax );
  6421.  
  6422.        /* Set the scroll bar position and range */
  6423.  
  6424.        case SBM_SETSCROLLBAR:
  6425.          (**hctl).contrlMin = SHORT1FROMMP(mp2);
  6426.          (**hctl).contrlMax = SHORT2FROMMP(mp2);
  6427.          /* fall through */
  6428.  
  6429.        /* Set the scroll bar position only */
  6430.  
  6431.        case SBM_SETPOS:
  6432.          if( MYWNDOF(hwnd).flStyle & WS_VISIBLE )
  6433.            SetCtlValue( hctl, SHORT1FROMMP(mp1) );
  6434.          else
  6435.            (**hctl).contrlValue = SHORT1FROMMP(mp1);
  6436.          fTrackThumb = FALSE;
  6437.          return MRFROMSHORT(1);
  6438.  
  6439.        /* Handle mouse button down: call TrackControl to track it */
  6440.  
  6441.        case WM_BUTTON1DOWN:
  6442.          ptl.x = SHORT1FROMMP(mp1);
  6443.          ptl.y = SHORT2FROMMP(mp1);
  6444.          MpmMapMacOfPtl( hwnd, &point, &ptl );
  6445.          sPart = FindControl( point, PWINOFHWND(hwnd), &hctl1 );
  6446.          ASSERT( sPart  &&  hctl1 == hctl,
  6447.                  "MpmFnwpScrollBar: FindControl failed" );
  6448.          _hwndTrack = hwnd;
  6449.          if( sPart == inThumb )
  6450.          {
  6451.            fTrackThumb = TRUE;
  6452.            sInitValue = (**hctl).contrlValue;
  6453.            if( TrackControl( hctl, point, NULL ) )
  6454.              MpmTrackScrollBarNotify( SB_SLIDERPOSITION );
  6455.            fTrackThumb = FALSE;
  6456.          }
  6457.          else
  6458.          {
  6459.            TrackControl( hctl, point, (ProcPtr)MpmTrackScrollBar );
  6460.            MpmTrackScrollBarNotify( SB_ENDSCROLL );
  6461.          }
  6462.          return 0L;
  6463.  
  6464.        /* Handle scroll bar creation: call NewControl to create it */
  6465.  
  6466.        case WM_CREATE:
  6467.          rect.left = rect.top = 0;
  6468.          if( MYWNDOF(hwnd).flStyle & SBS_VERT )
  6469.            rect.right = _alSysVal[SV_CXVSCROLL], rect.bottom = 100;
  6470.          else
  6471.            rect.bottom = _alSysVal[SV_CYHSCROLL], rect.right = 100;
  6472.          hctl = NewControl( PWINOFHWND(hwnd), &rect, _szNull,
  6473.                             MYWNDOF(hwnd).flStyle & WS_VISIBLE,
  6474.                             0, 0, 1, scrollBarProc, (long)hwnd );
  6475.          if( ! hctl )
  6476.            return MRFROMSHORT(1);
  6477.          MYWNDOF(hwnd).hctl = hctl;
  6478.          return 0L;
  6479.  
  6480.        /* Destroy scroll bar */
  6481.  
  6482.        case WM_DESTROY:
  6483.          MYWNDOF(hwnd).hctl = NULL;
  6484.          DisposeControl( hctl );
  6485.          return 0L;
  6486.  
  6487.        /* Handle window movement: use MoveControl to move the bar */
  6488.  
  6489.        case WM_MOVE:
  6490.          MpmQueryMacRect( hwnd, &rect );
  6491.          if( MYWNDOF(hwnd).flStyle & WS_VISIBLE )
  6492.            MoveControl( hctl, rect.left, rect.top );
  6493.          else
  6494.          {
  6495.            prect = &(**hctl).contrlRect;
  6496.            prect->right  += rect.left - prect->left;
  6497.            prect->left    = rect.left;
  6498.            prect->bottom += rect.top  - prect->top;
  6499.            prect->top     = rect.top;
  6500.          }
  6501.          return 0L;
  6502.  
  6503.        /* Show or hide the scroll bar */
  6504.  
  6505.        case WM_SHOW:
  6506.          if( mp1 )
  6507.            ShowControl( hctl );
  6508.          else
  6509.            HideControl( hctl );
  6510.          return 0L;
  6511.  
  6512.        /* Handle window resizing: use SizeControl to resize it */
  6513.  
  6514.        case WM_SIZE:
  6515.          if( SHORT1FROMMP(mp2) && SHORT2FROMMP(mp2) )
  6516.          {
  6517.            if( MYWNDOF(hwnd).flStyle & WS_VISIBLE )
  6518.              SizeControl( hctl,
  6519.                           SHORT1FROMMP(mp2),
  6520.                           SHORT2FROMMP(mp2) );
  6521.            else
  6522.            {
  6523.              prect = &(**hctl).contrlRect;
  6524.              prect->right  = prect->left + SHORT1FROMMP(mp2);
  6525.              prect->bottom = prect->top  + SHORT2FROMMP(mp2);
  6526.            }
  6527.          }
  6528.          return 0L;
  6529.      }
  6530.  
  6531.      return WinDefWindowProc( hwnd, msg, mp1, mp2 );
  6532.  }
  6533.  
  6534.  
  6535.  Figure 11:  Function to Load the Menu Bar from the Resource File
  6536.  
  6537.  LOCAL BOOL MpmMenuLoad( hwndFrame, idResources )
  6538.      HWND        hwndFrame;
  6539.      USHORT      idResources;
  6540.  {
  6541.      MbarHandle  hmbar;
  6542.      MbarPtr     pmbar;
  6543.      SHORT       sMenuCount;
  6544.      MenuHandle  hmenu;
  6545.  
  6546.      hmbar = (MbarHandle)GetNewMBar( idResources );
  6547.      if( ! hmbar )
  6548.        return FALSE;
  6549.  
  6550.      sMenuCount = MENUCOUNTOFHMBAR( hmbar );
  6551.      ASSERT( sMenuCount > 0,
  6552.              "MpmMenuLoad: null menu bar?" );
  6553.  
  6554.      SetMenuBar( hmbar );
  6555.      DisposHandle( (Handle)hmbar );
  6556.  
  6557.      hmbar = (MbarHandle)MenuList;
  6558.  
  6559.      hmenu = (**hmbar).mlist[0].hmenu;
  6560.  
  6561.      ASSERT( hmenu,
  6562.              "MpmMenuLoad: no apple menu?" );
  6563.  
  6564.      _sAppleCount = CountMItems( hmenu );
  6565.  
  6566.      AddResMenu( hmenu, 'DRVR' );
  6567.  
  6568.      DrawMenuBar();
  6569.  
  6570.      return TRUE;
  6571.  }
  6572.  
  6573.  
  6574.  Figure 12:  Function to Handle Mouse Events
  6575.  
  6576.  LOCAL BOOL MpmMsgMouse( pqmsg, pEvent, msg )
  6577.      PQMSG       pqmsg;
  6578.      EventRecord *pEvent;
  6579.      USHORT      msg;
  6580.  {
  6581.      SHORT       sArea;
  6582.      POINTL      ptl;
  6583.      WindowPeek  pwin;
  6584.      USHORT      fid, usHitHi;
  6585.      HWND        hwnd;
  6586.  
  6587.      sArea = FindWindow( pEvent->where, &pwin );
  6588.  
  6589.      fid = usHitHi = 0;
  6590.      ptl = pqmsg->ptl;
  6591.      pqmsg->hwnd = hwnd = ( pwin ? HWNDOFPWIN(pwin) : _hwndDesktop );
  6592.  
  6593.      switch( sArea )
  6594.      {
  6595.        case inContent:
  6596.          WinMapWindowPoints( _hwndDesktop, hwnd, &ptl, 1 );
  6597.          MpmMsgFindChild( pqmsg, &ptl );
  6598.          hwnd = pqmsg->hwnd;
  6599.          break;
  6600.  
  6601.        case inDesk:
  6602.          break;
  6603.  
  6604.        case inDrag:
  6605.          fid = FID_TITLEBAR;
  6606.          break;
  6607.  
  6608.        case inGoAway:
  6609.          fid = FID_SYSMENU;
  6610.          break;
  6611.  
  6612.        case inGrow:
  6613.          fid = FID_SIZEBORDER;
  6614.          break;
  6615.  
  6616.        case inMenuBar:
  6617.          hwnd = _hwndMenu;
  6618.          if( ! hwnd )
  6619.            return FALSE;
  6620.          break;
  6621.  
  6622.        case inSysWindow:
  6623.          SystemClick( pEvent, pwin );
  6624.          return FALSE;
  6625.  
  6626.        case inZoomIn:
  6627.        case inZoomOut:
  6628.          usHitHi = sArea;
  6629.          fid = FID_MINMAX;
  6630.          break;
  6631.  
  6632.        default:
  6633.          return FALSE;
  6634.      }
  6635.  
  6636.      if( fid )
  6637.      {
  6638.        hwnd = WinWindowFromID( hwnd, fid );
  6639.        ASSERT( hwnd,
  6640.                "MpmMsgMouse: missing frame control" );
  6641.      }
  6642.  
  6643.      if( MYWNDOF(hwnd).flStyle & WS_DISABLED )
  6644.        return FALSE;
  6645.  
  6646.      pqmsg->hwnd = hwnd;
  6647.      pqmsg->msg  = msg;
  6648.      pqmsg->mp1  = MPFROM2SHORT( ptl.x, ptl.y );
  6649.      pqmsg->mp2  = MPFROM2SHORT( HT_NORMAL, usHitHi );
  6650.  
  6651.      return TRUE;
  6652.  }
  6653.  
  6654.  
  6655.  Figure 13:  Function to Get a PS Handle for hwnd
  6656.  
  6657.  HPS APIENTRY WinGetPS( hwnd )
  6658.      HWND        hwnd;
  6659.  {
  6660.      GrafPtr     pgraf;
  6661.      RgnHandle   hrgn;
  6662.      Rect        rect;
  6663.  
  6664.      ASSERT( ! ( _ps1.flags & PSF_INUSE ),
  6665.              "WinGetPS: PS already in use" );
  6666.  
  6667.      if( ! hwnd  ||  hwnd == HWND_DESKTOP )
  6668.        hwnd = _hwndDesktop;
  6669.  
  6670.      if( ! MpmValidateWindow(hwnd) )
  6671.        return NULL;
  6672.  
  6673.      /* Clear the cache PS and mark it as in use */
  6674.  
  6675.      memzero( &_ps1 );
  6676.      _ps1.hwnd = hwnd;
  6677.      _ps1.flags |= PSF_INUSE;
  6678.  
  6679.      /* Copy the Mac window's GrafPort */
  6680.  
  6681.      if( hwnd == _hwndDesktop )
  6682.        GetWMgrPort( &pgraf );
  6683.      else
  6684.        pgraf = &PWINOFHWND(hwnd)->port;
  6685.  
  6686.      _ps1.port = *pgraf;
  6687.  
  6688.      _ps1.port.visRgn = NewRgn();
  6689.      CopyRgn( pgraf->visRgn, _ps1.port.visRgn );
  6690.  
  6691.      _ps1.port.clipRgn = NewRgn();
  6692.      CopyRgn( pgraf->clipRgn, _ps1.port.clipRgn );
  6693.  
  6694.      /* Clip the visRgn down to this window's rectangle in case it's
  6695.         a child window */
  6696.  
  6697.      if( ! ( MYWNDOF(hwnd).flStyle & WS_PARENTCLIP ) )
  6698.      {
  6699.        hrgn = NewRgn();
  6700.        MpmQueryMacRect( hwnd, &rect );
  6701.        RectRgn( hrgn, &rect );
  6702.        SectRgn( _ps1.port.visRgn, hrgn, _ps1.port.visRgn );
  6703.        DisposeRgn( hrgn );
  6704.      }
  6705.  
  6706.      /* Handle WS_CLIPCHILDREN and WS_CLIPSIBLINGS here? */
  6707.  
  6708.      return _hps1;
  6709.  }
  6710.  
  6711.  
  6712.  Figure 14:  Module Containing Mac Equivalents of PM GPI Functions
  6713.  
  6714.  /*-----------------------------------------------------------------*/
  6715.  /* MpmGPI.c                                                        */
  6716.  /* GPI functions                                                   */
  6717.  /*-----------------------------------------------------------------*/
  6718.  
  6719.  #include "MacPM.h"
  6720.  
  6721.  /*-----------------------------------------------------------------*/
  6722.  /* Draw a character string at the current position                 */
  6723.  /*-----------------------------------------------------------------*/
  6724.  
  6725.  LONG APIENTRY GpiCharString( hps, lLen, pch )
  6726.      HPS         hps;
  6727.      LONG        lLen;
  6728.      PCH         pch;
  6729.  {
  6730.      if( ! MpmValidatePS(hps) )
  6731.        return GPI_ERROR;
  6732.  
  6733.      thePort = PGRAFOFHPS(hps);
  6734.  
  6735.      DrawText( pch, 0, (SHORT)lLen );
  6736.  
  6737.      return GPI_OK;
  6738.  }
  6739.  
  6740.  /*-----------------------------------------------------------------*/
  6741.  /* Draw a character string at the specified position               */
  6742.  /*-----------------------------------------------------------------*/
  6743.  
  6744.  LONG APIENTRY GpiCharStringAt( hps, pptl, lLen, pch )
  6745.      HPS         hps;
  6746.      PPOINTL     pptl;
  6747.      LONG        lLen;
  6748.      PCH         pch;
  6749.  {
  6750.      if( ! GpiMove( hps, pptl ) )
  6751.        return GPI_ERROR;
  6752.  
  6753.      return GpiCharString( hps, lLen, pch );
  6754.  }
  6755.  
  6756.  /*-----------------------------------------------------------------*/
  6757.  /* Erase a PS on the screen.                                       */
  6758.  /*-----------------------------------------------------------------*/
  6759.  
  6760.  BOOL APIENTRY GpiErase( hps )
  6761.      HPS         hps;
  6762.  {
  6763.      if( ! MpmValidatePS(hps) )
  6764.        return FALSE;
  6765.  
  6766.      thePort = PGRAFOFHPS(hps);
  6767.  
  6768.      EraseRgn( thePort->visRgn );
  6769.  
  6770.      return TRUE;
  6771.  }
  6772.  
  6773.  /*-----------------------------------------------------------------*/
  6774.  /* Set the drawing position to *pptl.                              */
  6775.  /*-----------------------------------------------------------------*/
  6776.  
  6777.  BOOL APIENTRY GpiMove( hps, pptl )
  6778.      HPS         hps;
  6779.      PPOINTL     pptl;
  6780.  {
  6781.      PMYWND      pwnd;
  6782.  
  6783.      if( ! MpmValidatePS(hps) )
  6784.        return FALSE;
  6785.  
  6786.      thePort = PGRAFOFHPS(hps);
  6787.      pwnd = PMYWNDOF(HWNDOFHPS(hps));
  6788.  
  6789.      MoveTo( (SHORT)pptl->x, (SHORT)( pwnd->cy - pptl->y ) );
  6790.  
  6791.      return TRUE;
  6792.  }
  6793.  
  6794.  /*-----------------------------------------------------------------*/
  6795.  /* Pick up the current font metrics and return it in *pfm.         */
  6796.  /* This is kind of hokey and only initializes some of the fields   */
  6797.  /* properly!                                                       */
  6798.  /*-----------------------------------------------------------------*/
  6799.  
  6800.  BOOL APIENTRY GpiQueryFontMetrics( hps, lLen, pfm )
  6801.      HPS         hps;
  6802.      LONG        lLen;
  6803.      PFONTMETRICS pfm;
  6804.  {
  6805.      GrafPtr     pgraf;
  6806.      FMInput     fmi;
  6807.      FMOutPtr    pfmo;
  6808.  
  6809.      if( ! MpmValidatePS(hps) )
  6810.        return FALSE;
  6811.  
  6812.      pgraf = PGRAFOFHPS( hps );
  6813.  
  6814.      fmi.family    = pgraf->txFont;
  6815.      fmi.size      = pgraf->txSize;
  6816.      fmi.face      = pgraf->txFace;
  6817.      fmi.needBits  = FALSE;
  6818.      fmi.device    = 0;
  6819.      fmi.numer.h   = 1;
  6820.      fmi.numer.v   = 1;
  6821.      fmi.denom.h   = 1;
  6822.      fmi.denom.v   = 1;
  6823.  
  6824.      pfmo = FMSwapFont( &fmi );
  6825.      ASSERT( pfmo,
  6826.              "GpiQueryFontMetrics: FMSwapFont failed?" );
  6827.  
  6828.      memzero( pfm );
  6829.  
  6830.      pfm->lAveCharWidth      =  /* wrong, but make it same as max */
  6831.      pfm->lMaxCharInc        = pfmo->widMax;
  6832.      pfm->lMaxBaselineExt    = pfmo->ascent + pfmo->descent
  6833.                                + pfmo->leading;
  6834.      pfm->lMaxDescender      = pfmo->descent;
  6835.  
  6836.      /* more later... */
  6837.  
  6838.      return TRUE;
  6839.  }
  6840.  
  6841.  /*-----------------------------------------------------------------*/
  6842.  /* Make sure hps is a valid PS handle.                             */
  6843.  /*-----------------------------------------------------------------*/
  6844.  
  6845.  LOCAL BOOL MpmValidatePS( hps )
  6846.      HPS         hps;
  6847.  {
  6848.      if( hps != _hps1 )
  6849.      {
  6850.        ERROR( "Invalid PS handle!" );
  6851.        return FALSE;
  6852.      }
  6853.  
  6854.      return TRUE;
  6855.  }
  6856.  
  6857.  /*-----------------------------------------------------------------*/
  6858.  
  6859.  
  6860.  ───────────────────────────────────────────────────────────────────────────
  6861.  Development Tools for the Macintosh
  6862.  ───────────────────────────────────────────────────────────────────────────
  6863.  
  6864.  For OS/2 and Windows, there's not much question of which compilers and
  6865.  libraries to use: Microsoft C and Macro Assembler (MASM), along with the
  6866.  Windows and OS/2 Software Development Kits (SDKs). This situation may change
  6867.  as other vendors provide OS/2 and Windows tools. On the Mac, the choice is
  6868.  less clear. There are a variety of good development environments for C and
  6869.  assembler, but the best are Lightspeed C and the Macintosh Programmer's
  6870.  Workshop (MPW). MPW provides the utmost in programmability and
  6871.  customizability; its main window is a command shell, where you can type in
  6872.  simple commands or edit and run complex scripts. You can open additional
  6873.  windows to edit C or assembler code, or to save and edit script files.
  6874.  Besides using MPW to code standalone Mac applications, it's also very easy
  6875.  to write "tools," specialized applications that run only under the MPW shell
  6876.  and extend its functionality.
  6877.  
  6878.  As great as MPW is, my personal choice is Lightspeed C. Though it lacks the
  6879.  programmable shell of MPW, Lightspeed C lives up to its name in speeding up
  6880.  the development process. Turnaround from a source change to running the
  6881.  code is blazingly fast, and the editor and compiler are closely integrated
  6882.  so the editor knows things like which source file defines a particular
  6883.  function. You can write inline assembly right in the middle of C code,
  6884.  making utility routines easy to code.
  6885.  
  6886.  Best of all is the source code debugger in the new 3.0 version. The hot item
  6887.  is its data window. Like the CodeView(R) Watch window, it's basically a
  6888.  list of expressions and their values, and it's updated each time the program
  6889.  stops (for example, at a breakpoint). Double-click on a structure or array
  6890.  in this window, and it opens up a structure or array window. This is almost
  6891.  identical to the new "??" command in the CodeView debugger, but you can open
  6892.  up as many of these windows as you like and keep them open while you run
  6893.  your program, watching your data as you go. In any of the data windows, you
  6894.  can type in new values for any of the watched expressions. Data windows and
  6895.  the source code window share screen space with the application fairly
  6896.  cleanly; on a Mac with two monitors, the data windows can appear on the
  6897.  second monitor. The expression evaluator, both for new values and for the
  6898.  expressions themselves, knows every bit of compiler information, and you
  6899.  can use preprocessor macros and symbols freely in your expressions. Using
  6900.  Lightspeed C with the source debugger requires at least two megabytes of
  6901.  RAM and MultiFinder.
  6902.  
  6903.  There are a few Mac utilities I wouldn't leave home without, whether using
  6904.  MPW or Lightspeed C. TMON is a machine-level debugger that is the perfect
  6905.  complement to Lightspeed's source debugger. It's easy to use, with a nice
  6906.  windowed interface, but lacks nothing in debugging power──a must for any
  6907.  serious Mac programmer. QuicKeys is a keyboard/mouse macro program.
  6908.  Although Apple now includes a keyboard macro program with the Mac System
  6909.  software, QuicKeys is still my favorite. None of my Mac software could do
  6910.  much with the extra keys on Apple's Extended Keyboard, but a few minutes
  6911.  with QuicKeys and I had them all working the way I wanted. I even have all
  6912.  the same Edit menu shortcuts (Ctrl-Ins and friends) as in Windows and PM,
  6913.  making life less confusing when I switch back and forth. QuicKeys is worth
  6914.  buying just to get a lesson in how to design a superb utility package. One
  6915.  last tidbit is The Programmer's Online Companion, a handy on-line reference
  6916.  to all the Mac Toolbox and operating system calls. If you ever use Inside
  6917.  Macintosh, you need this. Be sure to get the new version with the Mac II
  6918.  calls.
  6919.  
  6920.  Of course, one remaining problem is transferring files between the PC and
  6921.  the Mac. The best bet for this would be either Lap-Link Mac or MacLink(R).
  6922.  I've been using Lap-Link Mac, and it does a good job. It comes with a serial
  6923.  cable that will connect any Mac to any PC. The cable has both 9-pin and 25-
  6924.  pin connectors on the PC end, and 9-pin and mini-DIN connectors on the Mac
  6925.  end; it makes for a funny looking cable, but hooking the computers up is
  6926.  painless this way. Transfers are quick and reliable, and the program can
  6927.  convert between the Mac and PC text file formats. The only thing I don't
  6928.  like about Lap-Link Mac is the user interface. Everything is controlled
  6929.  from the PC side──the Mac program is a slave──and, while it's reasonably
  6930.  powerful and convenient, the user interface takes some getting used to.
  6931.  MacLink is probably worth looking into, though I haven't tried it out
  6932.  myself.
  6933.  
  6934.  Product Information
  6935.  
  6936.  XVT-the Extensible Virtual Toolkit
  6937.  Advanced Programming Institute Ltd.
  6938.  P.O. Box 17665
  6939.  Boulder, CO 80308
  6940.  (303) 443-4223
  6941.  
  6942.  Currently available for Macintosh and Windows. OS/2, X Windows, and other
  6943.  versions planned. The "Technical Overview of XVT," by Marc Rochkind is
  6944.  available free from API and is itself an education in portability issues.
  6945.  
  6946.  Lightspeed C
  6947.  Symantec, Inc., Think Technologies Division
  6948.  135 South Road
  6949.  Bedford, MA 01730
  6950.  (617) 275-4800
  6951.  
  6952.  Macintosh Programmer's Workshop
  6953.  Apple Programmer's and Developer's Association (APDA)
  6954.  290 S.W. 43rd St.
  6955.  Renton, WA 98055
  6956.  (206) 251-6548
  6957.  
  6958.  TMON
  6959.  ICOM Simulations, Inc.
  6960.  648 S. Wheeling Road
  6961.  Suite 10
  6962.  Wheeling, IL 60090
  6963.  (312) 520-4440
  6964.  
  6965.  QuicKeys
  6966.  CE Software, Inc.
  6967.  P.O. Box  65580
  6968.  West Des Moines, IA 50265
  6969.  (515) 224-1995
  6970.  
  6971.  The Programmer's Online Companion
  6972.  Addison-Wesley Publishing Company, Inc.
  6973.  Jacob Way
  6974.  Reading, MA 01867
  6975.  (617) 944-3700
  6976.  
  6977.  
  6978.  Using the OS/2 Environment to Develop DOS and OS/2 Applications
  6979.  
  6980.  ───────────────────────────────────────────────────────────────────────────
  6981.  Also see the following related articles:
  6982.    An OS/2 Command Reference
  6983.    Configuring OS/2
  6984.    Exploring the OS/2 SDK
  6985.    Exploring the OS/2 Programmer's Toolkit
  6986.  ───────────────────────────────────────────────────────────────────────────
  6987.  
  6988.  Richard Hale Shaw
  6989.  
  6990.  You've been thinking of switching to an OS/2 system for applications
  6991.  development but don't know where to start. You've bought an OS/2 system and
  6992.  aren't sure how to install and configure it for programming. Its
  6993.  multitasking, multithreaded capabilities intrigue you but seem formidable.
  6994.  
  6995.  Well, they are a bit formidable. OS/2 offers significant productivity
  6996.  gains whether you're developing software for either DOS or OS/2, but it
  6997.  demands that you rethink many of the programming assumptions you made when
  6998.  writing DOS applications.
  6999.  
  7000.  This article will help you get started. First, we'll investigate OS/2 as a
  7001.  program development environment: its hardware and software requirements,
  7002.  installing and configuring OS/2, and utilizing multiple sessions for
  7003.  program development. In subsequent articles we'll study the OS/2
  7004.  application program interface (API), in particular the Video Input/Output
  7005.  (VIO), Keyboard (KBD), and Mouse (MOU) subsystems by writing useful programs
  7006.  that rely on them. We'll learn to write bound applications for both real and
  7007.  protected mode, and wrap up by putting together the key ideas into a real
  7008.  and useful OS/2 application.
  7009.  
  7010.  This series of articles assumes a working knowledge of C and DOS, but not
  7011.  OS/2. Our goal is to develop several programs and tools that will help to
  7012.  instill a working knowledge of the OS/2 API and some of the issues
  7013.  critical to writing OS/2 applications. You will need a C compiler, the
  7014.  Microsoft(R) OS/2 Programmer's Toolkit, and an OS/2 system. By the time we
  7015.  finish you'll be ready to consider the next level of programming──the
  7016.  Presentation Manager.
  7017.  
  7018.  
  7019.  What are the Benefits?
  7020.  
  7021.  It's well known that OS/2 operates in the protected mode of the Intel(R)
  7022.  80286 processor──the 80386 also supports 286 protected mode──and, of course,
  7023.  you can use OS/2 to develop protected-mode programs. But there are also
  7024.  advantages to using OS/2 as a host development environment for
  7025.  applications that run in MS-DOS(R) 8086/8088 real mode.
  7026.  
  7027.  Probably the most immediate benefit of OS/2 is the ability to use multiple
  7028.  program development sessions. Each program can run as an independent
  7029.  process under OS/2, which gives the program its own screen group or
  7030.  session, and also endows each session with a logical keyboard and console
  7031.  interface. OS/2 ties these to the real keyboard and video screen when the
  7032.  process is running in the foreground. Processes running in the background
  7033.  continue uninterrupted unless awaiting keyboard input (which they can get
  7034.  when switched back into the foreground).
  7035.  
  7036.  As a result, most of the mundane tasks done while writing and developing a
  7037.  program, including tasks that tie up specific machine resources, may be
  7038.  carried out more efficiently in multiple program sessions. Compiling a
  7039.  program or grepping for program references, for instance, can run in a
  7040.  session that you switched into the background. This leaves you free to
  7041.  pursue other tasks, such as editing another part of the program or
  7042.  compiling a different program altogether.
  7043.  
  7044.  With the OS/2 multisession environment, you can dedicate a session to each
  7045.  facet of program development. It's possible to create separate sessions for
  7046.  the program editor, the compiler or assembler, for utilities, the debugger,
  7047.  and the program test environment. Or, you might create a session whose sole
  7048.  purpose is to act as a convenient, readily-available command prompt, for
  7049.  running utilities and copying disks (you can even relegate disk formatting
  7050.  to the background). You need not exit or terminate your program editor to
  7051.  start the compilation of a source code module. Nor must you terminate a
  7052.  debugging session in order to look up a source code reference with your
  7053.  editor.
  7054.  
  7055.  Since each OS/2 session contains its own independent environment, prompt,
  7056.  path, and screen configuration, you can customize a session and tailor it to
  7057.  the tasks which are to run in it. For example, you can set a compiler
  7058.  session's path and environment variables for library and source file
  7059.  searches, and optimize them to most quickly navigate the appropriate
  7060.  directories. You can create a program editor session which only allows the
  7061.  editor to know (via environment variables) about a specific set of files and
  7062.  directories, and which would differ from a session used for testing a new
  7063.  program.
  7064.  
  7065.  Creating several sessions to perform different tasks is easy under OS/2; in
  7066.  fact, it's addictive. Using the same tricks that were available under MS-DOS
  7067.  (a few simple tools and ANSI environment strings), you can configure the
  7068.  command prompt, screen colors, and screen modes for each session
  7069.  independently. Thus, you can readily identify each, even when rapidly
  7070.  switching from one session to another.
  7071.  
  7072.  For example, a compilation session might be set to a 43-line screen mode
  7073.  with a dark background and highlighted prompt. This would allow a quick
  7074.  perusal of compiler warning and error messages when switching between
  7075.  sessions, and it can make it easier to discern the progress of a compiler
  7076.  session when processing a large number of files. Or, the command-prompt
  7077.  session described above might be a stark black-on-white.
  7078.  
  7079.  Under OS/2 you can dedicate a separate session to each program that needs a
  7080.  unique environment (the concept of the "environment" takes on a meaning
  7081.  more akin to that in UNIX(R) operating environments). In fact, it's not
  7082.  unusual to run two different compilers at once.
  7083.  
  7084.  Running multiple editing sessions can further enhance your productivity.
  7085.  Most contemporary program editors boast the ability to display and edit
  7086.  multiple files in multiple windows. By creating more than one editing
  7087.  session, you can literally double or triple the number of files that can be
  7088.  displayed or edited at a time, and you can do so without cluttering the
  7089.  screen.
  7090.  
  7091.  To take this a step further, I've found it useful to actually edit, compile,
  7092.  and debug two entirely different programs simultaneously. Although it
  7093.  might seem counterproductive, this is helpful when one of the programs is
  7094.  exceptionally large (over 100Kb) and takes an inordinate amount of time to
  7095.  compile and link, in which case there is plenty of time to switch one
  7096.  compilation session into the background and edit or test a completely
  7097.  different program in the foreground. While foreground sessions get priority
  7098.  attention from the CPU under OS/2, you'll be amazed how quickly a well-
  7099.  behaved OS/2 application will run in the background. (The definition of a
  7100.  "well-behaved" OS/2 application will become evident over the course of the
  7101.  next few articles in this series.──Ed.)
  7102.  
  7103.  
  7104.  The DOS Session
  7105.  
  7106.  OS/2 can be configured to create a special real-mode session that supports
  7107.  a hybrid version of MS-DOS 3.x, known alternately as the Compatibility Box
  7108.  or the DOS session. This session is only active as a foreground process
  7109.  (that is, when displayed on the screen). It's frozen in the background
  7110.  whenever you switch to another session and OS/2 returns the processor to
  7111.  protected mode.
  7112.  
  7113.  To programs running in the DOS session, the operating environment appears
  7114.  to be DOS, although OS/2 keeps some control over the machine. Since the
  7115.  processor runs the DOS session in real mode, the "fast and loose" DOS
  7116.  operating environment is still available, making it possible for a DOS
  7117.  program running in the DOS session to abuse it and cause problems──with even
  7118.  greater ramifications than under normal DOS. Further, some tools behave
  7119.  acceptably under DOS but may not under OS/2. A number of program editors,
  7120.  for instance, poll the keyboard continuously in order to speedily process
  7121.  keyboard input. This is fine in a single-tasking environment like DOS.
  7122.  Under OS/2 these applications can bring the system to its knees; by
  7123.  directly polling the hardware from the DOS session, they force the CPU to
  7124.  assign them too many time slices. Thus, while they remain speedy, the lack
  7125.  of CPU attention given elsewhere can slow background processes (such as a
  7126.  program compile) to a crawl.
  7127.  
  7128.  A well-written OS/2 editor will create a thread to get keyboard input.
  7129.  Since OS/2 knows which screen group "owns" the thread, it will "block" the
  7130.  thread until keyboard input is available for it. Thus other processes will
  7131.  proceed normally (whether in the background or the foreground), getting
  7132.  their appropriate share of CPU time.
  7133.  
  7134.  While the DOS session facilitates the move to OS/2, product development is
  7135.  far easier when you have tools that run in, and take advantage of, OS/2's
  7136.  multitasking environment. For instance, if you have a UNIX-like grep
  7137.  command that only runs under DOS, you will only be able to run it in the DOS
  7138.  session, and it will be frozen whenever the DOS session is pushed into the
  7139.  background.
  7140.  
  7141.  If such a command were written for OS/2, it would not only continue to run
  7142.  in a background OS/2 session, but you could have it take advantage of
  7143.  threads──multitasking within the process itself──by assigning, for example,
  7144.  one thread to open and search a file, and another to write the results to
  7145.  the output. It will not only cooperate with other OS/2 processes competing
  7146.  for CPU attention and machine resources, but it will also be significantly
  7147.  more efficient than its single thread DOS counterpart.
  7148.  
  7149.  While the DOS session can provide almost a full 640Kb DOS environment, the
  7150.  limitations of trying to debug and run large programs under it are the
  7151.  same as those found under MS-DOS 3.x. It may be impossible to debug a
  7152.  program with Microsoft CodeView(R) debugger in the DOS session if the
  7153.  program is too large, or if too many modules are compiled for debugging.
  7154.  This is never an issue, however, when debugging large programs with the
  7155.  protected-mode version of CodeView, due to OS/2's virtual memory.
  7156.  
  7157.  
  7158.  Startup Requirements
  7159.  
  7160.  Now that you've decided to develop programs under OS/2, what do you really
  7161.  need in order to get started? And what fits in that special "it would sure
  7162.  be great──if I could afford it" category?
  7163.  
  7164.  Our goal is to become as proficient as possible at writing OS/2
  7165.  applications. However, there really are two ways to get there, one
  7166.  sufficient and one preferred. It's not unlike the difference between flying
  7167.  coach and first class. The sufficient method includes everything necessary
  7168.  to successfully write OS/2 programs. Preferred (or first class) is
  7169.  definitely more expensive but it can make the difference between getting up
  7170.  and running, and getting up and running very quickly. Either way, to run and
  7171.  utilize OS/2 you need a machine with at least an Intel 80286 or 80386 CPU;
  7172.  2.5Mb of RAM; 20Mb of disk space; a high-density disk drive (either a 1.44
  7173.  megabyte 3.5" or a 1.2 megabyte 5.25"); and, of course, a video monitor and
  7174.  keyboard.
  7175.  
  7176.  The following hardware is preferred (and will immediately prove valuable):
  7177.  3+ megabytes of RAM; 40+ megabytes of disk space; IBM(R) EGA, VGA, or 100
  7178.  percent compatible display adapter (the EGA adapter should have 128Kb of
  7179.  RAM); and a mouse.
  7180.  
  7181.  When it comes to software, the choices are far more varied. To adequately
  7182.  develop programs under OS/2, you'll need the following tools: a compiler
  7183.  (C, Pascal, etc.) that provides OS/2 support; an assembler; a protected-
  7184.  mode linker; a protected-mode program editor; a protected-mode debugger;
  7185.  other utilities such as make, grep, etc.; OS/2 Version 1.0 (Version 1.1,
  7186.  which includes the Presentation Manager, will do, but it's not necessary to
  7187.  get started).
  7188.  
  7189.  As you're probably aware, most of the Microsoft languages already offer
  7190.  versions that will produce protected-mode executables. This includes
  7191.  Microsoft C Optimizing Compiler Version 5.1, a "bound" application capable
  7192.  of producing programs that run under both operating environments (that
  7193.  is, it runs in either real or protected mode). If you are an experienced C
  7194.  programmer, you'll have a number of assembly language subroutines.
  7195.  Microsoft Macro Assembler Version 5.1 (MASM) will fill the bill for an
  7196.  assembler (like C 5.1, it is a bound application). The new Microsoft
  7197.  incremental linker, LINK4, which can incrementally (and therefore more
  7198.  quickly) link object files to produce an executable for either OS/2 or DOS,
  7199.  is included with both Microsoft C 5.1 and MASM 5.1.
  7200.  
  7201.  The Microsoft Editor, a powerful protected-mode program editor, is also
  7202.  included with both MASM and Microsoft C. I should mention, however, that
  7203.  other excellent protected-mode editors, such as Lugaru's Epsilon(TM) and
  7204.  the ME Editor from Magma Systems are now available or beginning to appear.
  7205.  
  7206.  
  7207.  Toolkit or SDK?
  7208.  
  7209.  Microsoft offers two ways of getting the necessary tools for OS/2 program
  7210.  development. The Microsoft OS/2 Software Development Kit, known as SDK (see
  7211.  "Exploring the OS/2 SDK"), offers a comprehensive collection of virtually
  7212.  every tool needed, from OS/2 (Versions 1.0 and 1.1──the latter including
  7213.  Presentation Manager and LAN Manager) to the C compiler and assembler, to
  7214.  utilities, etc. This is the preferred package──but the cost of the SDK
  7215.  ($3,000) can be prohibitive to the individual programmer or developer.
  7216.  
  7217.  The alternative is to purchase each individual tool you need. MSJ readers
  7218.  who already own the most recent versions of Microsoft C and/or MASM have a
  7219.  head start, since many of the other required tools come with the products.
  7220.  These, plus OS/2 itself, are adequate for developing OS/2 programs. You will
  7221.  also need to purchase the Microsoft OS/2 Programmer's Toolkit (PTK). The
  7222.  PTK (see "Exploring the OS/2 Programmer's Toolkit"), which is also included
  7223.  in the SDK, contains many of the essential tools and example programs
  7224.  found in the SDK──most importantly the complete Programmer's Reference guide
  7225.  to all of the OS/2 API calls.
  7226.  
  7227.  If you don't expect to develop Presentation Manager or LAN Manager
  7228.  applications right away (a reasonable expectation if you are just getting
  7229.  started), you might consider purchasing the PTK as a less expensive (about
  7230.  $300) alternative to the SDK, in addition to the other required tools. Note
  7231.  that the PTK requires a high-level language compiler (such as C 5.1), an
  7232.  editor, and OS/2 itself.
  7233.  
  7234.  
  7235.  Installing OS/2
  7236.  
  7237.  Once you're ready to install OS/2, you'll encounter a number of options,
  7238.  primarily hinging on whether you already have MS-DOS on the machine on which
  7239.  you're installing OS/2. These options let you:
  7240.  
  7241.    ■  Default to booting OS/2 (boot DOS from a floppy).
  7242.  
  7243.    ■  Default to booting DOS (boot OS/2 from a floppy).
  7244.  
  7245.    ■  Have the system prompt you for which operating system to run at boot
  7246.       time. (Dual-boot mode, available with early SDK versions of OS/2 and
  7247.       some other manufacturers' versions──for example, Zenith's; the
  7248.       operating system can be booted from the hard disk.)
  7249.  
  7250.  If you expect to rarely boot DOS again, the first option might be a viable
  7251.  one. The last option is usually the most convenient. Keep in mind that the
  7252.  DOS session will still be available to you when OS/2 is running; the option
  7253.  only refers to booting either MS-DOS 3.x or later or OS/2 from the hard
  7254.  disk. Not all versions of OS/2 provide a dual-boot mode option of
  7255.  installation, and dual-boot will not be supported in retail versions of
  7256.  OS/2 sold by IBM.
  7257.  
  7258.  For the sake of simplicity, I will assume you want to install OS/2 on your
  7259.  MS-DOS development machine, and still be able to run MS-DOS free and clear
  7260.  of OS/2-in other words, the dual-boot installation option. In actuality, it
  7261.  doesn't matter which installation method is chosen-whichever is handiest
  7262.  should be the guiding line. While there may be other steps involved,
  7263.  depending on the number and size of the hard disks in your system,
  7264.  installing OS/2 basically involves booting the OS/2 Installation Disk and
  7265.  answering a few questions. (It's infinitely less complex than installing C
  7266.  5.1, with its plethora of options.) The installation program will prompt
  7267.  you for other disks as needed. And that's it.
  7268.  
  7269.  If you install OS/2 to include the dual-boot option, then the installation
  7270.  program reconfigures the boot sector of your drive. Once you reboot after
  7271.  successfully installing OS/2, the following message will appear on your
  7272.  screen:
  7273.  
  7274.    Boot: Enter=OS/2, ESC=DOS
  7275.  
  7276.  That's all that it takes to choose between OS/2 and DOS. If you chose to
  7277.  install OS/2 from either the hard disk or a floppy, then obviously OS/2 will
  7278.  come up directly.
  7279.  
  7280.  
  7281.  Exploring the OS/2 Files
  7282.  
  7283.  As you start exploring your system after installing OS/2, you'll find a
  7284.  number of changes. First of all, you'll find some additions to the
  7285.  subdirectory structure in the root of your boot drive, as shown in the
  7286.  diagram in Figure 1. Each directory has a specific use and meaning as
  7287.  indicated. It is possible that this directory structure may change somewhat
  7288.  in the future, but as it stands, it serves our purposes for discussion in
  7289.  this article.
  7290.  
  7291.  The installation program places a number of files in the root directory of
  7292.  your boot drive, or in one of the subdirectories mentioned above. While
  7293.  quite a few of these are OS/2 utilities (see "An OS/2 Command Reference"),
  7294.  a number are worth noting. First you'll find two hidden files, as in DOS
  7295.  environments: OS2BIO.COM and OS2DOS.COM. (Note the DOS hidden files,
  7296.  IBMBIO.COM and IBMDOS.COM, are untouched.) These will always be in the root
  7297.  directory if dual-boot mode was selected. Retail releases of OS/2, however,
  7298.  may have different names for these hidden system files.
  7299.  
  7300.  The next few changes are especially meaningful in light of what I said
  7301.  earlier about peaceful coexistence between DOS and OS/2.
  7302.  
  7303.  CONFIG.OS2 is similar to the CONFIG.SYS found under MS-DOS (see "Configuring
  7304.  OS/2"). OS/2 processes it at system startup. If you are not using the dual-
  7305.  boot mode, this file would be called CONFIG.SYS under OS/2.
  7306.  
  7307.  STARTUP.CMD is OS/2's counterpart to the MS-DOS AUTOEXEC.BAT. It is
  7308.  processed after CONFIG.SYS and lets you start other programs and processes
  7309.  and further modify the OS/2 environment.
  7310.  
  7311.  OS2INIT.CMD is the batch file that is run whenever you initiate a new
  7312.  protected-mode session, and its commands execute at the start of each
  7313.  session.
  7314.  
  7315.  AUTOEXEC.OS2, like AUTOEXEC.BAT its MS-DOS counterpart, runs upon initiation
  7316.  of the real-mode session and is used to configure the environment of the
  7317.  DOS session. This file retains its AUTOEXEC.BAT filename under OS/2 when
  7318.  the dual-boot mode is not used.
  7319.  
  7320.  Two of these files are OS/2 batch files. OS/2 requires that batch files have
  7321.  a CMD extension to distinguish them from their DOS counterparts. The
  7322.  following are among the remaining files added by the installation program,
  7323.  and require special note:
  7324.  
  7325.  CMD.EXE is the OS/2 command interpreter (akin to COMMAND.COM under
  7326.  MS-DOS).
  7327.  
  7328.  HARDERR.EXE is the OS/2 critical error handler.
  7329.  
  7330.  PMSHELL.EXE is the Presentation Manager Task Manager (for OS/2 Version
  7331.  1.1).
  7332.  
  7333.  SHELL.EXE is the OS/2 Program Selector (for OS/2 Version 1.0).
  7334.  
  7335.  SWAPPER.DAT is the OS/2 virtual memory swap file.
  7336.  
  7337.  SWAPPER.EXE is the OS/2 virtual memory manager.
  7338.  
  7339.  
  7340.  Configuring OS/2
  7341.  
  7342.  There are several ways to configure OS/2 via CONFIG.OS2 (see
  7343.  "Configuring OS/2"). You'll want to have loaded the appropriate device
  7344.  drivers (via DEVICE=), and you'll want paths to both the real-mode command
  7345.  processor (SHELL= as under MS-DOS) and to the protected-mode command
  7346.  processor. The latter usually appears as shown in Figure 2A. SHELL.EXE is
  7347.  the OS/2 Program Selector (provided with OS/2 Version 1.0), and CMD.EXE is
  7348.  the OS/2 command processor. Since one of SHELL.EXE's arguments is CMD.EXE,
  7349.  the former will use the latter for loading and running programs.
  7350.  
  7351.  In order to cover all bases, I should mention that SDKs shipped at the time
  7352.  of this writing (October, 1988) include installation programs for a beta
  7353.  version of Presentation Manager, which is included in OS/2 Version 1.1. The
  7354.  prerelease of the PM Shell is relatively functional, and if you install its
  7355.  Program Starter and Task Manager (both are PM's alternative to the Program
  7356.  Selector), your PROTSHELL entry will probably look like that shown in
  7357.  Figure 2B.
  7358.  
  7359.  In addition, the following are "musts" if you're going to use OS/2 for
  7360.  program development:
  7361.  
  7362.    ■  Use the OS/2 disk cache to gain a performance advantage. This is done
  7363.       by placing DISKCACHE=x into CONFIG.OS2, where x is the size in
  7364.       kilobytes of the disk cache. I recommend at least 256 (on a
  7365.       development machine with 2.5 meg of RAM).
  7366.  
  7367.    ■  To run the protected-mode version of CodeView, you will need IOPL=YES
  7368.       in CONFIG.OS2, which gives additional IO data privileges to programs
  7369.       like CodeView.
  7370.  
  7371.    ■  If you plan to use or create dynamic-link libraries, you
  7372.       should have LIBPATH=path, where path points to the location of DLLs
  7373.       (the installation program will take care of this for you in most
  7374.       versions).
  7375.  
  7376.    ■  If you want to conserve memory when the DOS session is unnecessary,
  7377.       set PROTECTONLY=YES in CONFIG.OS2. This prevents the DOS session from
  7378.       being allocated or run, even though you might have a SHELL= statement
  7379.       designating the real-mode command processor. As an alternative, you
  7380.       can keep the DOS session available but limit the memory allocated to it
  7381.       via RMSIZE=x, where x is the total memory up to 640Kb that can be
  7382.       allocated to it.
  7383.  
  7384.    ■  If you're running processes with a lot of threads, you can increase the
  7385.       number of threads available from OS/2 with THREADS=x, where x is the
  7386.       total number of threads available. Keep in mind that there is a slight
  7387.       memory overhead relative to the total number of threads allocated.
  7388.  
  7389.  
  7390.  RUN and START
  7391.  
  7392.  OS/2 lets you prestart programs via the RUN or START commands. RUN is a
  7393.  directive placed in CONFIG.OS2 that lets OS/2 initiate a program or
  7394.  process. For example, the line shown in Figure 2C will run the OS/2 print
  7395.  spooler when OS/2 is done processing CONFIG.OS2. Since CONFIG.OS2 lets you
  7396.  have more than one RUN statement in it, you can initiate a number of
  7397.  processes directly from CONFIG.OS2.
  7398.  
  7399.  One limitation to RUN, however, is that it is not capable of running CMD
  7400.  files-so that while you can run a program directly, you can't configure its
  7401.  environment. You get around this by running the CMD file as an argument
  7402.  to the OS/2 command processor, CMD.EXE. Thus, you could use a line like
  7403.  that in Figure 2D to start a process that needs a CMD file to initialize
  7404.  its environment.
  7405.  
  7406.  Alternatively, you could use the START command. START lets you initiate
  7407.  another session from the OS/2 command line. If you wanted to create a CMD
  7408.  file that initiated several processes (without having to initiate each one
  7409.  from the Program Selector), the CMD file might contain the lines shown in
  7410.  Figure 2E. If you are going to regularly start a series of programs, the
  7411.  preferred method is to place the START commands in STARTUP.CMD.
  7412.  
  7413.  
  7414.  Switching Programs
  7415.  
  7416.  Getting around in OS/2 is easy. You can press Ctrl-Esc at any time to bring
  7417.  up the Program Selector (the Task Manager under PM), which displays a
  7418.  configurable list of programs to initiate, and a list of sessions already
  7419.  active. You then select the session you wish to switch to, or the process
  7420.  you wish to start. Personally, I find it's easiest to switch between active
  7421.  sessions by pressing Alt-Esc. This lets you rapidly "hot-key" from session
  7422.  to session until you reach the one you want to work with. There is very
  7423.  little delay in this method even if you are hot-keying across the DOS
  7424.  session, regardless of the hardware you're using. This is fairly rapid on a
  7425.  10 MHz 286 machine with 2.5Mb of memory and over a half-dozen sessions
  7426.  running, and it's especially fast when you consider the overhead that OS/2
  7427.  imposes on the processor when switching from protected mode to real mode and
  7428.  back. Yet this is exactly what happens when you hot-key from a protected-
  7429.  mode session to the DOS session to another protected-mode session with Alt-
  7430.  Esc.
  7431.  
  7432.  
  7433.  Using OS/2
  7434.  
  7435.  Once you have OS/2 up and running you can start exploring some of its
  7436.  advantages over DOS. As shown in "An OS/2 Command Reference," a number of
  7437.  carry-over commands from DOS have been enhanced to accept multiple
  7438.  arguments. In addition, some of the substitution facilities that were
  7439.  available only in BAT files under DOS are available on the command line
  7440.  under OS/2. If the environment has this entry:
  7441.  
  7442.    LIB=c:\MSC\OS2\LIB
  7443.  
  7444.  followed by the command
  7445.  
  7446.    CD %LIB%
  7447.  
  7448.  then it would change to the \MSC\OS2\LIB directory on the C: drive. Under
  7449.  DOS, this would only have worked in a BAT file.
  7450.  
  7451.  Another convenience is the DETACH command. DETACH lets you initiate a
  7452.  background process from the command line. For instance,
  7453.  
  7454.    DIR | SORT > dsort.txt
  7455.  
  7456.  initiates a process that runs DIR, pipes the result to SORT, and redirects
  7457.  the output to a file. So,
  7458.  
  7459.    DETACH DIR | SORT > dsort.txt
  7460.  
  7461.  places the entire process in a background session. DETACH always assumes
  7462.  that the process it runs does not require any keyboard input. After
  7463.  initiating the process, DETACH prints the process' ID for the task and
  7464.  returns to the process that started it──in this case, the OS/2 command line.
  7465.  Thus, it's easy to relegate complex tasks to the background and let the user
  7466.  proceed as usual.
  7467.  
  7468.  Another advantage becomes apparent whenever you need to perform an OS/2
  7469.  command line operation while running a program. You can always Ctrl-Esc
  7470.  to the Program Selector to bring up another OS/2 command line. This is
  7471.  especially convenient when you need to format floppy disks or delete some
  7472.  files without leaving a program. The formatting, once started, will proceed
  7473.  smoothly in the background after you Alt-Esc back to the session you left
  7474.  earlier. If you find this convenient, you may want to make a habit of
  7475.  initiating an OS/2 command session when you begin work (or RUN one from
  7476.  CONFIG.OS2 before the Program Selector appears).
  7477.  
  7478.  Another OS/2 plus is that the command-line facilities have been enhanced in
  7479.  order to allow more complex expressions, as mentioned in "An OS/2 Command
  7480.  Reference." For instance, the command line in Figure 3 would conditionally
  7481.  build a sorted list of protected-mode utilities-conditionally, because the
  7482.  DIR command would not be issued if the drive and directory change were not
  7483.  successful. Users can run the entire process as a background task by
  7484.  placing a DETACH command in front of the entire command. Additionally, the
  7485.  foreground session would return on the original drive in the original
  7486.  directory since the drive and directory change would take place in the
  7487.  background session; remember, such changes are local to a session and do
  7488.  not affect other sessions.
  7489.  
  7490.  Finally, you'll find that OS/2 CMD files have also been enhanced over MS-DOS
  7491.  BAT files, especially with the new SETLOCAL and ENDLOCAL commands. SETLOCAL
  7492.  saves the current state of the logged-in disk drive and current working
  7493.  directory; ENDLOCAL restores these to the state saved by SETLOCAL. Thus you
  7494.  could write a CMD file like this:
  7495.  
  7496.    @echo off
  7497.    setlocal
  7498.    c: && cd\ha && ha
  7499.    endlocal
  7500.  
  7501.  This would log in drive C, change to the \HA directory, and run the program
  7502.  HA.EXE. When the program has completed execution, the original drive and
  7503.  directory will be restored to the session. These commands are similar to the
  7504.  PUSHDIR/POPDIR commands available in some UNIX shell environments.
  7505.  
  7506.  
  7507.  No Caffeine
  7508.  
  7509.  The best reason for not using OS/2 as a development environment is that
  7510.  it'll eliminate coffee breaks during long program compilations; you can
  7511.  always do something else! While its multitasking capabilities are extensive,
  7512.  OS/2 is an extremely productive, in fact fun, work environment. Coupled with
  7513.  the applications that are taking advantage of it, this fact will draw large
  7514.  numbers of users to OS/2, and it's a good reason for you to begin using OS/2
  7515.  to develop programs now, if you haven't already started.
  7516.  
  7517.  In the next article of this series, we'll discuss setting up the C compiler
  7518.  and explore the issues surrounding the writing of your first protected-mode
  7519.  application: a multithreaded version of HELLO.C.
  7520.  
  7521.  (As this article was going to press, IBM and Microsoft announced the
  7522.  official release of OS/2 Version 1.1. The new installation of OS/2 Version
  7523.  1.1 and Presentation Manager changes some of the setup features of earlier
  7524.  releases. For example, dual-boot mode is no longer an available option, the
  7525.  system files IBMBIO.COM and IBMDOS.COM have been renamed to OS2LDR and
  7526.  OS2KRNL respectivley, and the initial directory structure may be somewhat
  7527.  different than that described here.──Ed.)
  7528.  
  7529.  
  7530.  Figure 1:  Standard OS/2 Directory Structure
  7531.  
  7532.                            ┌───────────────────\(Root)────────┬──────────┐
  7533.                            │                                  │          │
  7534.                                                             SPOOL     (other)
  7535.     ┌──────────┬──────────OS/2─────────┬──────────┐
  7536.     │          │           │           │          │
  7537.    BIN        DEV         LIB        PBIN       RBIN
  7538.  
  7539.  \OS2         for OS\2 and Presentation Manager files
  7540.  \OS2\BIN     for bound utilities
  7541.  \OS2\DEV     for storing device drivers
  7542.  \OS2\LIB     for dynamic-link libraries
  7543.  \OS2\PBIN    for protected-mode only utilities
  7544.  \OS2\RBIN    for real-mode only utilities
  7545.  \SPOOL       used by the OS/2 print spooler
  7546.  
  7547.  
  7548.  Figure 2:  Examples of OS/2 CONFIG.SYS Entries
  7549.  
  7550.   2A
  7551.   PROTSHELL=C:\SHELL.EXE C:\CMD.EXE /K C:\OS2INIT.CMD
  7552.  
  7553.   2B
  7554.   PROTSHELL=C:\OS2\PMSHELL.EXE C:\OS2\PBIN\CMD.EXE /K C:\OS2INIT.CMD
  7555.  
  7556.   2C
  7557.   RUN=c:\os2\pbin\spool.exe
  7558.  
  7559.   2D
  7560.   RUN=c:\cmd.exe /c c:\os2\pbin\mestart.cmd
  7561.  
  7562.   2E
  7563.   start "HyperACCESS" /c ha
  7564.   start "Paradox" /c pdoxos2 -multi
  7565.   start "OS/2 Command Line"
  7566.  
  7567.  
  7568.  Figure 3:  Under OS/2 multiple commands can be executed from the command
  7569.             line.
  7570.  
  7571.  c: && cd \os2\pbin && dir *.exe *.cmd | sort > tools.lst
  7572.  
  7573.  
  7574.  ───────────────────────────────────────────────────────────────────────────
  7575.  An OS/2 Command Reference
  7576.  ───────────────────────────────────────────────────────────────────────────
  7577.  
  7578.  Most of the traditional MS-DOS commands have been retained in OS/2's
  7579.  protected-mode environment. However, several commands have been enhanced and
  7580.  a number of new ones have been added.
  7581.  
  7582.  The following commands have been enhanced over their DOS and real-mode
  7583.  counterparts to allow multiple arguments:
  7584.  
  7585.    ■ DEL         ■ MD/MKDIR
  7586.    ■ DIR         ■ RD/RMDIR
  7587.    ■ TYPE        ■ VOL
  7588.  
  7589.  For instance you can delete all of the .C, .H, and .ASM files in a directory
  7590.  with:
  7591.  
  7592.    DEL *.c *.h *.asm
  7593.  
  7594.  (Obviously, I'm not recommending that you try out this particular sequence.)
  7595.  In addition, the MODE command now allows for the setting of time-out values
  7596.  and hardware handshaking details.
  7597.  
  7598.  The following are new commands, available in OS/2's protected mode:
  7599.  
  7600.    &&
  7601.  
  7602.  The && is used to conditionally chain commands:
  7603.  
  7604.    dir x && del x
  7605.  
  7606.  This will delete x only if the first command was successful.
  7607.  
  7608.    &
  7609.  
  7610.  The & can unconditionally chain commands:
  7611.  
  7612.    dir x & del x
  7613.  
  7614.  Here, OS/2 will attempt both commands. The outcome of one does not depend on
  7615.  the other.
  7616.  
  7617.  || can conditionally execute commands, so that in:
  7618.  
  7619.    copy x y || copy a y
  7620.  
  7621.  The second command will only execute if the first command is unsuccessful.
  7622.  
  7623.    ()
  7624.  
  7625.  Parentheses can group commands for chaining and execution:
  7626.  
  7627.    del x || (attrib -r x & del x)
  7628.  
  7629.  In this case, if the deletion is unsuccessful (because x's read/write
  7630.  attribute is on), OS/2 will attempt to clear the read/write attribute of x
  7631.  and, if successful, delete it.
  7632.  
  7633.    ^
  7634.  
  7635.  The ^ will normalize special characters. Thus:
  7636.  
  7637.    ^> file.txt
  7638.  
  7639.  will be seen as "> file.txt". OS/2 will not see the > as a special character
  7640.  and will not attempt redirection.
  7641.  
  7642.    ANSI [ON | OFF]
  7643.  
  7644.  This command toggles support for ANSI escape sequences. It is provided in
  7645.  lieu of ANSI.SYS, the real-mode device driver.
  7646.  
  7647.    CMD [options] [arguments]
  7648.  
  7649.  Used to run the protected-mode command processor. Options include /c to
  7650.  ensure return to the primary command processor, and /k to retain the new
  7651.  command processor in memory after executing the specified commands.
  7652.  
  7653.    CREATEDD
  7654.  
  7655.  Creates a memory dump diskette.
  7656.  
  7657.    DETACH [program [arguments]]
  7658.  
  7659.  This command runs protected-mode programs and processes in the background.
  7660.  However, detached programs will not be able to get keyboard input and all
  7661.  screen output from them is lost. A process ID is returned when successfully
  7662.  initiated.
  7663.  
  7664.    DPATH path;...
  7665.  
  7666.  Similar to the APPEND command in real mode, DPATH creates a path
  7667.  applications can search for opening data files, via the environment
  7668.  variable, DPATH. Applications that use DPATH must search the environment to
  7669.  obtain its values.
  7670.  
  7671.    ENDLOCAL
  7672.  
  7673.  For batch file usage, this command restores the current working directory
  7674.  and environment to the one saved by a previous SETLOCAL command.
  7675.  
  7676.    EXTPROC
  7677.  
  7678.  This command defines an alternative command processor for use in processing
  7679.  a batch file.
  7680.  
  7681.    KEYB
  7682.  
  7683.  Specifies an alternative keyboard layout for users outside of the United
  7684.  States.
  7685.  
  7686.    SETLOCAL
  7687.  
  7688.  For batch file usage, this command preserves the current drive, directory,
  7689.  and environment for later restoration by ENDLOCAL during a batch file run.
  7690.  
  7691.    SPOOL
  7692.  
  7693.  Initiates the background print spooler.
  7694.  
  7695.    START "session name" [/c] [command [options]]
  7696.  
  7697.  Initiate a new protected-mode session. The session's name (specified as an
  7698.  argument) will appear in the Program Selector's menu.
  7699.  
  7700.    TRACE <on | off> [code[,...]]
  7701.  
  7702.  Toggles system tracing on or off.
  7703.  
  7704.    TRACEFMT
  7705.  
  7706.  Displays the contents of the system trace buffer in LIFO order.
  7707.  
  7708.  ───────────────────────────────────────────────────────────────────────────
  7709.  Configuring OS/2
  7710.  ───────────────────────────────────────────────────────────────────────────
  7711.  
  7712.  As with CONFIG.SYS, under MS-DOS users can extensively configure OS/2 by
  7713.  placing commands in CONFIG.OS2. Several of these, such as break and fcbs,
  7714.  are virtually identical to their DOS counterparts and are still available in
  7715.  OS/2's real mode. Others──such as buffers, device, and shell──are also
  7716.  available in protected mode. Device is used to install OS/2 device drivers.
  7717.  In the context of OS/2's CONFIG.OS2 file, shell designates the real-mode
  7718.  command processor, as it did for MS-DOS' COMMAND.COM.
  7719.  
  7720.  In addition to these, however, there are a number of new commands available
  7721.  for configuring OS/2.
  7722.  
  7723.  DISKCACHE This command enables the diskcache and sets its size. By default
  7724.  the cache is not enabled.
  7725.  
  7726.  IOPL Gives protected-mode applications requesting them additional data IO
  7727.  privileges. IOPL=YES must be in your CONFIG.OS2 file to run the protected-
  7728.  mode version of CodeView, CVP. The default is YES.
  7729.  
  7730.  LIBPATH Similar in form to PATH and DPATH, LIBPATH is a CONFIG.OS2 command
  7731.  that specifies the location of dynamic-link libraries. The default LIBPATH
  7732.  is the root of the boot drive.
  7733.  
  7734.  MAXWAIT This command sets the maximum wait time, in seconds; this is the
  7735.  time that must elapse before the scheduler increases a process' priority
  7736.  when it hasn't been recognized. The default is 3 and can range from 1-255
  7737.  seconds.
  7738.  
  7739.  MEMMAN This command toggles virtual-memory swapping. Turning swapping on
  7740.  will let OS/2 run more processes than will fit into memory, and
  7741.  automatically turn move on. Having move on will let OS/2 temporarily move
  7742.  data segments. However, performance tends to improve with swapping off, up
  7743.  to a point. The default is SWAP, MOVE if the boot drive is a hard disk, and
  7744.  noswap/nomove if the boot drive is a floppy.
  7745.  
  7746.  PAUSEONERROR Lets OS/2 display error messages while processing CONFIG.OS2.
  7747.  The default is YES.
  7748.  
  7749.  PRIORITY This command influences the manner in which a process gets time
  7750.  priority. OS/2 classifies priorities into three classes: time-critical,
  7751.  normal, and idle-time. There are 32 priority levels in each class, and
  7752.  priorities in the normal class can be adjusted.
  7753.  
  7754.  Setting PRIORITY=ABSOLUTE in CONFIG.OS2 stops the system from dynamically
  7755.  changing the priority of processes within the normal class.
  7756.  
  7757.  If PRIORITY=DYNAMIC, OS/2 will try to determine which process most needs CPU
  7758.  resources at any given time slice. If a process seems to have a lower
  7759.  priority than it should, OS/2 will increase the priority level of that
  7760.  process. The default is PRIORITY=DYNAMIC.
  7761.  
  7762.  PROTECTONLY Lets you run both protected-mode and real-mode applications if
  7763.  PROTECTONLY=NO. Although you can't run real-mode applications if PROTECTONLY
  7764.  is set to NO, this will free some memory used by the real-mode compatibility
  7765.  box (up to 640Kb). The default is NO.
  7766.  
  7767.  PROTSHELL This command designates the protected-mode command processor and
  7768.  program selector. The default is:
  7769.  
  7770.    PROTSHELL=SHELL.EXE CMD.EXE /K OS2INIT.CMD
  7771.  
  7772.  REM Used to place comments in CONFIG.OS2.
  7773.  
  7774.  RMSIZE Configures the amount of memory reserved for the real-mode
  7775.  environment. The default is based on the total memory available, usually the
  7776.  amount of memory installed below 1024Kb (either 512Kb or 640Kb).
  7777.  
  7778.  RUN This command lets protected-mode programs or processes start during
  7779.  system initialization. Thus with several RUN= commands in CONFIG.OS2, a
  7780.  number of programs can be started before the OS/2 Program Selector appears.
  7781.  Note that OS/2 processes all DEVICE= directives before any programs are run,
  7782.  and that batch files (.CMD) are not runnable from CONFIG.OS2.
  7783.  
  7784.  SWAPPATH The swap file is the temporary file OS/2 uses to implement its
  7785.  virtual memory scheme. This command specifies the location of the swap file
  7786.  (which should be placed on a hard disk). Note that the minimum swap file
  7787.  size is 640Kb, and that this command is ignored if swapping is disabled (see
  7788.  MEMMAN).
  7789.  
  7790.  THREADS A thread is the portion of an application or process that OS/2 can
  7791.  schedule. A process always contains at least one thread of execution, and
  7792.  can contain multiple threads. These act like small programs that perform
  7793.  particular tasks in each process.
  7794.  
  7795.  The command sets the maximum number of threads available at one time. Note
  7796.  that OS/2 uses about 14 threads. The system sets the default and each
  7797.  additional thread uses a small amount of memory.
  7798.  
  7799.  TIMESLICE A time slice is the interval of time OS/2 uses to schedule the
  7800.  threads of a process. The TIMESLICE command specifies the minimum and
  7801.  maximum amounts of time in milliseconds (thousands of a second) that OS/2
  7802.  can dedicate to one process before checking on other processes. If only one
  7803.  value is specified, both values will be set to it. OS/2 sets the default.
  7804.  The minimum value must be greater than 31 and the maximum must be greater
  7805.  than or equal to the minimum. Setting TIMESLICE=500 will make the scheduler
  7806.  wait a half-second before checking other threads for a priority higher than
  7807.  the threads currently executing.
  7808.  
  7809.  TRACE OS/2 keeps a record of the actions it takes processing hardware
  7810.  interrupts and functions, which can be helpful during program development.
  7811.  System tracing, as this is called, can be toggled via TRACE=ON or TRACE=OFF
  7812.  in CONFIG.OS2. You can trace individual types of events by specifying their
  7813.  related event codes after the ON. You can issue TRACE=ON followed by
  7814.  TRACE=OFF X,Y where X and Y are event codes corresponding to events that you
  7815.  do not want traced. Tracing is OFF by default. The event codes must fall
  7816.  into the range 0-255.
  7817.  
  7818.  TRACEBUF This command sets, in kilobytes, the size of the trace buffer to be
  7819.  allocated for the TRACE command.
  7820.  
  7821.  In addition to the commands mentioned above, the following commands are also
  7822.  available, but are really only applicable outside of the U.S.
  7823.  
  7824.  CODEPAGE Selects a code page.
  7825.  
  7826.  COUNTRY Selects time, date, and currency conventions.
  7827.  
  7828.  DEVINFO Prepares a device for use with code page.
  7829.  
  7830.  Note that OS/2 ignores the FILES and LASTDRIVE commands found in DOS
  7831.  CONFIG.SYS files.
  7832.  
  7833.  
  7834.  Sample CONFIG.OS2 Files
  7835.  
  7836.  The following are two examples of CONFIG.OS2. The first is for OS/2 Version
  7837.  1.0, while the second, for OS/2 Version 1.1 including the PM, is nearly
  7838.  identical to that produced by the installation program.
  7839.  
  7840.  Sample 1:
  7841.  
  7842.    REM CONFIG.OS2 FOR OS/2 v.1.0
  7843.    buffers=50
  7844.    shell=c:\command.com /P /E:500
  7845.    protshell=c:\shell.exe cmd.exe /k c:\os2init.cmd
  7846.    libpath=c:\os2\dll
  7847.    device=c:\os2\dev\mousea03.sys
  7848.    device=c:\os2\dev\pointdd.sys
  7849.    device=c:\os2\dev\com01.sys
  7850.    run=c:\os2\pbin\spool.exe c:\os2\spool /o:lpt1
  7851.    break=on
  7852.    files=25
  7853.    IOPL=YES
  7854.    TRACE=ON
  7855.    TRACEBUF=8
  7856.    run=c:\cmd.exe /c c:\cset
  7857.    diskcache=512
  7858.  
  7859.  Sample 2:
  7860.  
  7861.    REM CONFIG.OS2 FOR OS/2 v.1.1 and PM
  7862.    buffers=30
  7863.    shell=c:\os2\rbin\command.com /P /e:1024 c:\os2\rbin
  7864.    rem the following entry must be all on one line!
  7865.    protshell=c:\os2\pmshell.exe c:\os2\pbin\cmd.exe /k c:\os2init.cmd
  7866.    rmsize=640
  7867.    protectonly=NO
  7868.    break=OFF
  7869.    fcbs=16,8
  7870.    threads=64
  7871.    iopl=YES
  7872.    libpath=c:\os2\dll
  7873.    set path=c:\os2;c:\os2\bin;c:\os2\pbin
  7874.    memman=SWAP,MOVE
  7875.    diskcache=64
  7876.    maxwait=3
  7877.    swappath=c:
  7878.    device=c:\os2\dev\mousea03.sys
  7879.    device=c:\os2\dev\pointdd.sys
  7880.    device=c:\os2\dev\pmdd.sys
  7881.    device=c:\os2\dev\com01.sys
  7882.    device=c:\os2\dev\ega.sys
  7883.  
  7884.  ───────────────────────────────────────────────────────────────────────────
  7885.  Exploring the OS/2 SDK (Software Development Kit)
  7886.  ───────────────────────────────────────────────────────────────────────────
  7887.  
  7888.  The OS/2 Software Development Kit (SDK) is the single most comprehensive
  7889.  resource for OS/2 development tools and materials. While it costs $3,000,
  7890.  it's so complete that you may seriously want to consider it, depending on
  7891.  what types of OS/2 applications you (or your company) intend to develop.
  7892.  
  7893.  In essence, the SDK puts all the tools together in one place. If you expect
  7894.  to develop Presentation Manager or LAN Manager applications rather quickly,
  7895.  the SDK contains everything you need in one purchase. On the other hand, if
  7896.  you won't be developing Presentation Manager or LAN Manager programs right
  7897.  away, you may want to consider purchasing the necessary components
  7898.  separately.
  7899.  
  7900.  Below is a list of the components included in the SDK. Note that in addition
  7901.  to the Presentation Manager, the LAN Manager, and the OS/2 Programmer's
  7902.  Toolkit, both MS Windows and the Windows SDK are included. The SDK includes
  7903.  these since the Windows programming interface is very similar to that
  7904.  provided by the Presentation Manager. The rule of thumb is: get to know the
  7905.  Windows programming interface. Once you've done so, programming Presentation
  7906.  Manager will be considerably easier.
  7907.  
  7908.    ■  MS OS/2 Version 1.0; MS OS/2 Version 1.1 (including Presentation
  7909.       Manager); MS Windows 2.03; MS OnLine; MS OS/2 Version 1.1 Toolkit.
  7910.  
  7911.    ■  Tools: Microsoft C Version 5.1; Microsoft Assembler Version 5.1;
  7912.       CodeView (real and protected mode); Utilities: Editor, Linker,
  7913.       Librarian, Bind (for creating dual-mode applications), Make, Grep,
  7914.       Incremental Linker.
  7915.  
  7916.    ■  QuickHelp for OS/2
  7917.  
  7918.    ■  OS/2 Programmer's Toolkit (PTK) Version 1.0.
  7919.  
  7920.    ■  MS OS/2 LAN Manager: User's Guide; Programmer's Reference.
  7921.  
  7922.    ■  MS OS/2 Presentation Manager: Conversion Guide; Reference, Volume I;
  7923.       Reference, Volume II; Reference, Addendum; "Programming the OS/2
  7924.       Presentation Manager" (and companion disk), by Charles Petzold.
  7925.  
  7926.    ■  MS Windows Version 2.03 Software Development Kit (SDK): Learning Guide;
  7927.       Tools, Style Guide, Extensions; Programmer's Reference Guide; Windows
  7928.       Applications Tools.
  7929.  
  7930.    ■  Inside OS/2 by Gordon Letwin.
  7931.  
  7932.    ■  A subscription to Microsoft System Journal.
  7933.  
  7934.  ───────────────────────────────────────────────────────────────────────────
  7935.  Exploring the OS/2 Programmer's Toolkit
  7936.  ───────────────────────────────────────────────────────────────────────────
  7937.  
  7938.  The Microsoft OS/2 Programmer's Toolkit contains many of the tools required
  7939.  to build complex protected-mode applications under OS/2. The Toolkit
  7940.  complements and completes the minimal program development tools necessary to
  7941.  writing programs under OS/2. Included are:
  7942.  
  7943.    ■  Setup Guide and User's Guide
  7944.  
  7945.    ■  Programmer's Learning Guide and Programming Tools introduces writing C
  7946.       programs under OS/2. It presupposes experience in both. There are also
  7947.       discussions of how to create and build dynamic-link libraries.
  7948.  
  7949.    ■  Programmer's Reference includes all of the OS/2 system calls, etc.
  7950.  
  7951.    ■  QuickHelp
  7952.  
  7953.    ■  Sample Program examples demonstrate: memory allocation; multithreaded
  7954.       programming: thread examples in assembler and C, passing arguments to
  7955.       threads, using critical sections in threads, using DosKill and DosExit,
  7956.       suspending threads; controlling the speaker; accessing machine
  7957.       configuration and machine mode; accessing country information; aliasing
  7958.       code/data segments; accessing the machine date and time; Dynamic-Link
  7959.       Libraries in C and assembler; use of Exitlist; accessing volume
  7960.       information; accessing environment information; hello.c for OS/2;
  7961.       accessing process information; keyboard input; using monitors, pipes,
  7962.       and queues; moving files; accessing file handle information; handling
  7963.       sessions; shared memory; signal handling; register VIO subsystem;
  7964.       simple terminal emulation; simple screen editing; multithreaded text
  7965.       searching; directory navigation; listing files; LIFE game; EGA
  7966.       graphics; mouse interface.
  7967.  
  7968.    ■  Application Development Utilities includes, in addition to the
  7969.       appropriate header files and libraries, utilities for: creating dual-
  7970.       mode applications; searching libraries; searching files; creating
  7971.       import libraries; dumping .EXE headers; running the system trace;
  7972.       binding messages; dumping shared memory and files; searching for files;
  7973.       setting the keyboard delay and repeat rates.
  7974.  
  7975.    ■  A Subscription to MS(R) OnLine and two hours of usage.
  7976.  
  7977.  
  7978.  ────────────────────────────────────────────────────────────────────────────
  7979.  
  7980.  Volume 4 - Number 2
  7981.  
  7982.  ────────────────────────────────────────────────────────────────────────────
  7983.  
  7984.  Exploring Vector Fonts with the OS/2 Graphics Programming Interface
  7985.  
  7986.   Charles Petzold
  7987.  
  7988.  Let's begin with a question: What graphics programming language stores fonts
  7989.  as lines and curves (rather than bitmaps) and thus allows fonts to be
  7990.  arbitrarily stretched, rotated, outlined, filled with different patterns, or
  7991.  even used as clipping areas? One answer is obviously PostScript(R), Adobe
  7992.  Systems' page composition language implemented on many high-end laser
  7993.  printers (beginning with the Apple(R) LaserWriter(R)) and Allied
  7994.  Corporation's Linotronic(R) phototypesetters. Over the past few years,
  7995.  PostScript has become the language of choice for computer manipulation of
  7996.  fonts and text.
  7997.  
  7998.  An equally valid answer is GPI--the Graphics Programming Interface
  7999.  (referred to herein as GPI) component of the OS/2 Presentation Manager. This
  8000.  article shows you how to work with vector fonts in GPI and demonstrates many
  8001.  PostScript-like techniques. As we'll see, GPI has facilities to do virtually
  8002.  everything with fonts that you can do with PostScript. However, GPI does
  8003.  have a deficiency that I will discuss at the end of this article.
  8004.  
  8005.  The Trouble with Text
  8006.  
  8007.  The display of text is always the most problematic part of a graphics
  8008.  programming system. Unlike lines and polygons (which are merely mathematical
  8009.  constructs), text is rooted in a long tradition of aesthetic typography. In
  8010.  any computer graphics system, the goal must always be to display text that
  8011.  is as pleasing and as easy to read as a well-printed book. Yet, most
  8012.  computer output devices (such as video displays and printers) are digital
  8013.  media. The subtly shaped and rounded characters that comprise traditional
  8014.  fonts must be broken down into discrete pixels for storage and then
  8015.  reassembled on the output device. This often causes distortions in the
  8016.  appearance of the text.
  8017.  
  8018.  One major advantage of using a computer for this job is versatility. We can
  8019.  use a wide variety of fonts in various sizes and characteristics and modify
  8020.  these fonts for display. The extent to which we can modify fonts depends on
  8021.  the way in which the fonts are stored in memory.
  8022.  
  8023.  Images and Vectors
  8024.  
  8025.  A font is generally stored in computer (or printer) memory in one of two
  8026.  very different ways. First, a font can be stored as an image or bitmap. Each
  8027.  character of the font is simply a rectangular array of bits. The 0 bits
  8028.  generally correspond to the background around the character and the 1 bits
  8029.  correspond to the character itself. Second, a font can be stored in a vector
  8030.  or outline format in which each character is defined as a series of lines
  8031.  and curves that enclose areas. The character is displayed by drawing the
  8032.  outline on the output device and filling in the enclosed areas.
  8033.  
  8034.  Image and vector fonts have distinct advantages and disadvantages. Image
  8035.  fonts are always created for specific font sizes and specific device
  8036.  resolutions. The size of a particular image font cannot easily be changed.
  8037.  (For example, enlarging an image font by doubling the rows and columns of
  8038.  pixels often emphasizes the jaggedness of the characters.) Also, image fonts
  8039.  cannot be rotated except possibly by 90 degree increments.
  8040.  
  8041.  Vector fonts are much more malleable. Because they are defined as a series
  8042.  of lines and curves, vector fonts can be stretched or compressed to any size
  8043.  and can be rotated to any angle. Vector fonts are not tied to a particular
  8044.  output device resolution.
  8045.  
  8046.  In general, however, image fonts are more legible than vector fonts. Various
  8047.  techniques are used to design image fonts so they fool the eye into thinking
  8048.  the characters are smoother than they actually are. Vector
  8049.  fonts--particularly when displayed on low-resolution devices and scaled
  8050.  to small font sizes--can be adjusted only by mathematical algorithms,
  8051.  which currently are less capable than human font designers. Another
  8052.  advantage of image fonts is performance since vector fonts usually require
  8053.  much more processing time to draw each character.
  8054.  
  8055.  Most conventional laser printers store fonts as images, either within the
  8056.  printer or in font cartridges. The printer is restricted to specific font
  8057.  sizes and the characters cannot be arbitrarily rotated. Much more versatile
  8058.  are the fonts stored in PostScript-based printers. These fonts are stored as
  8059.  vectors. PostScript fonts can be stretched or compressed to any size, they
  8060.  can be arbitrarily rotated, filled with various patterns, and used for
  8061.  clipping.
  8062.  
  8063.  The GPI Fonts
  8064.  
  8065.  GPI can, of course, take advantage of fonts that are stored in and supported
  8066.  by output devices such as laser printers. But it also includes its own
  8067.  support of both image and vector fonts. The image fonts are expected because
  8068.  they are particularly suited for low-resolution video displays and dot
  8069.  matrix printers. Image fonts are an important part of most graphics
  8070.  programming systems (such as Microsoft Windows GDI).
  8071.  
  8072.  The addition of vector fonts in GPI is a real treat. GPI can use these
  8073.  vector fonts with any output device. Thus, various font techniques that
  8074.  previously have been restricted to PostScript printers are now possible with
  8075.  other laser printers and even the video display.
  8076.  
  8077.  OS/2 Version 1.1 is shipped with three resource-only dynamic-link libraries
  8078.  with FON extensions. fIn addition, the video display (DISPLAY.DLL) and
  8079.  printer device drivers may also contain fonts designed specifically for the
  8080.  device. For example, the default System Proportional font is stored in
  8081.  DISPLAY.DLL.
  8082.  
  8083.  If you want to use any of the fonts in the FON files, you must install the
  8084.  fonts from the Presentation Manager Control Panel program. It is only
  8085.  necessary to install one font from each of the three files, and you only
  8086.  need do this once.
  8087.  
  8088.  Each font has a face name, which is shown in quotation marks in Figure 1.
  8089.  Each of the image fonts is available in several point sizes and for several
  8090.  output devices: the CGA, EGA, VGA (and 8514/A), and IBM(R) Proprinter. GPI
  8091.  can synthesize variations of these fonts, such as italic or boldfaced
  8092.  versions. Vector fonts, however, need not be designed for a particular
  8093.  output device and point size because they can be arbitrary scaled. You'll
  8094.  note that italic and boldface versions of the vector fonts are also
  8095.  included.
  8096.  
  8097.  The vector fonts in GPI are similar in style to the Courier, Helvetica(R),
  8098.  and Times(R) fonts included in most PostScript printers.
  8099.  
  8100.  A little exploration of the font files will reveal that vector fonts are
  8101.  encoded as a series of GPI drawing orders. When drawing text with these
  8102.  fonts, GPI translates the drawing orders into GPI functions, usually
  8103.  GpiPolyLine to draw straight lines and GpiPolyFilletSharp to draw curves.
  8104.  
  8105.  
  8106.  
  8107.  The VECTFONT Program
  8108.  
  8109.  The VECTFONT program demonstrates the use of GPI vector fonts. For purposes
  8110.  of clarity, I've divided the program into several modules. The files that
  8111.  comprise the basic shell of the program are VECTFONT, VECTFONT.LNK,
  8112.  VECTFONT.DEF, VECTFONT.H, and VECTFONT.C. (These files are not shown here,
  8113.  but can be downloaded from any of our bulletin boards--Ed.) Figure 2
  8114.  shows the code fragment from VECTFONT.C that is responsible for creating a
  8115.  PS, and for sizing client windows.
  8116.  
  8117.  The VECTFONT Display menu lists 16 options. The first option (to display
  8118.  nothing) is the default. The other 15 options correspond to routines in the
  8119.  VF01.C through VF15.C files (described below). The VF00.C file (not shown
  8120.  here) contains some helper functions used by the routines in VF01.C through
  8121.  VF15.C.
  8122.  
  8123.  VECTFONT creates a micro presentation space during the WM_CREATE message
  8124.  using page units of PU_TWIPS. (Twips is a fabricated word standing for 20th
  8125.  of a point. A printer's point size is 1/72 inch, so page units correspond to
  8126.  1/1440 inch.) VECTFONT then modifies the page viewport rectangle so that a
  8127.  page unit corresponds to 1 point, which is the default coordinate system in
  8128.  PostScript.
  8129.  
  8130.  Although VECTFONT displays output to the screen, vector fonts are obviously
  8131.  better suited for laser printers. As you will see, the appearance of the
  8132.  fonts--even at 24 point sizes--is not nearly as good as the image
  8133.  fonts.
  8134.  
  8135.  You will also notice that several of the demonstration routines in VECTFONT
  8136.  require a few seconds to run. For anything other than a demonstration
  8137.  program, you would probably want to use a second thread of execution to
  8138.  avoid holding up message processing.
  8139.  
  8140.  Figure 2:
  8141.  
  8142.  case WM_CREATE:\|  hdc = WinOpenWindowDC (hwnd) ;
  8143.  
  8144.                           // Create PS use Twips page units
  8145.       sizl.cx = 0 ;
  8146.       sizl.cy = 0 ;
  8147.       hps = GpiCreatePS (hab, hdc, &sizl,
  8148.                               PU_TWIPS     | GPIF_DEFAULT |
  8149.                               GPIT_MICRO   | GPIA_ASSOC) ;
  8150.  
  8151.                           // Adjust Page Viewport for points
  8152.  
  8153.       GpiQueryPageViewport (hps, &rcl) ;
  8154.       rcl.xRight *= 20 ;
  8155.       rcl.yTop   *= 20 ;
  8156.       GpiSetPageViewport (hps, &rcl) ;
  8157.  
  8158.       hwndMenu = WinWindowFromID (
  8159.                       WinQueryWindow (hwnd, QW_PARENT,
  8160.                                       FALSE), FID_MENU) ;
  8161.       return 0 ;
  8162.  
  8163.  case WM_SIZE:
  8164.       ptlClient.x = SHORT1FROMMP (mp2) ;      // client width
  8165.       ptlClient.y = SHORT2FROMMP (mp2) ;      // client height
  8166.  
  8167.       GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptlClient);
  8168.       return 0 ;
  8169.  
  8170.  
  8171.  
  8172.  Selecting an Outline Font
  8173.  
  8174.  To use an outline font in a Presentation Manager program, you must first
  8175.  create a logical font and then select the font into your presentation space.
  8176.  The GpiCreateLogFont function creates a logical font and associates the font
  8177.  with a local ID (a number between 1L and 254L that you select). This
  8178.  function requires a pointer to a structure of type FATTRS (font attributes)
  8179.  that specifies the attributes of the font you want.
  8180.  
  8181.  To create a vector font, most of the fields in this structure can be set to
  8182.  zero. The most important fields are szFacename (which is set to the face
  8183.  name of the font, one of the names in the last column of Figure 1) and
  8184.  fsFontUse, which is set to the constant identifiers FATTR_FONTUSE_OUTLINE
  8185.  and FATTR_FONTUSE_TRANSFORMABLE, combined with the C bitwise OR operator.
  8186.  
  8187.  You may prefer using the CreateVectorFont function in VF00.C to create a
  8188.  vector font. This function requires only the presentation space handle, the
  8189.  local ID, and the face name:
  8190.  
  8191.  CreateVectorFont(hps, lcid, szFacename);
  8192.  
  8193.  After you create a logical font (using either GpiCreateLogFont or
  8194.  CreateVectorFont), you can select the font into the presentation space:
  8195.  
  8196.  GpiSetCharSet(hps, lcid);
  8197.  
  8198.  The lcid parameter is the local ID for the font. After the font is selected
  8199.  in the presentation space, you can alter the attributes of the font with
  8200.  various functions described below, obtain information about the font by
  8201.  calling GpiQueryFontMetrics, GpiQueryWidthTable, and GpiQueryTextBox; and
  8202.  use the font for text output with one of the text functions such as
  8203.  GpiCharStringAt.
  8204.  
  8205.  When you no longer need the font, you reselect the default font into the
  8206.  presentation space:
  8207.  
  8208.  GpiSetCharSet(hps, LCID_DEFAULT);
  8209.  
  8210.  and then delete the local ID associated with the font:
  8211.  
  8212.  GpiDeleteSetId(hps, lcid);
  8213.  
  8214.  In VECTFONT I always use the identifier LCID_MYFONT for the local ID. This
  8215.  is defined in VECTFONT.H as 1L.
  8216.  
  8217.  Scaling to a Point Size
  8218.  
  8219.  When you call GpiSetCharSet to select a vector font into the presentation
  8220.  space, the initial width and height of the font are based on the GPI
  8221.  character box, which defines a character width and height in page units. The
  8222.  default character box is based on the size of the default system font. You
  8223.  can change the character box size by calling GpiSetCharBox.
  8224.  
  8225.  An important point to note is that to get a correctly proportioned vector
  8226.  font, you must change the character box size. Do not use the default.
  8227.  Generally you set the height of the character box to the desired height of
  8228.  the font. If you want a vector font to have a normal width, set the width of
  8229.  the character box equal to the height. For a skinnier font, set the width of
  8230.  the character box less than the height; for a fatter font, set the width
  8231.  greater than the height.
  8232.  
  8233.  If you're working in page units of PU_PELS, you must also adjust the
  8234.  character box dimensions to account for differences in horizontal and
  8235.  vertical resolution of the output device. For this reason, it's much easier
  8236.  to work with vector fonts if you use one of the metric page units
  8237.  (PU_LOENGLISH, PU_HIENGLISH, PU_LOMETRIC, PU_HIMETRIC, or PU_TWIPS). With
  8238.  these page units, horizontal and vertical page units are the same. For
  8239.  example, suppose you're using page units of PU_TWIPS. This means that one
  8240.  page unit is equal to 1/20 of a point or 1/1440 inch. After selecting a
  8241.  vector font into the presentation space, you want to scale the font to 24
  8242.  points. You first define a structure of type SIZEF:
  8243.  
  8244.  SIZEF sizfx;
  8245.  
  8246.  The two fields of this structure, named cx and cy, are interpreted as 32-bit
  8247.  FIXED numbers; that is, the high 16 bits are interpreted as an integer, and
  8248.  the low 16 bits are interpreted as a fraction.
  8249.  
  8250.  If you want to scale the vector font to a 24-point height, you can use the
  8251.  MAKEFIXED macro to set to the fields of the structure like this:
  8252.  
  8253.  sizfx.cx = MAKEFIXED(24 * 20, 0);
  8254.  sizfx.cy = MAKEFIXED(24 * 20, 0);
  8255.  
  8256.  Multiplying by 20 is necessary to convert the point size you want to twips.
  8257.  Then call GpiSetCharBox:
  8258.  
  8259.  GpiSetCharBox(hps, &sizfx);
  8260.  
  8261.  After setting the character box, any character or text dimensions you obtain
  8262.  from GpiQueryFontMetrics, GpiQueryTextBox, and GpiQueryWidthTable will
  8263.  reflect the new font size.
  8264.  
  8265.  The ScaleVectorFont routine in VF00.C can help in scaling a vector font to a
  8266.  desired point size. The function will work with any page units, even
  8267.  PU_PELS. The second and third parameters to ScaleVectorFont specify a point
  8268.  size in units of 0.1 points. (For example, use 240 for a 24-point size.) If
  8269.  you want a normally proportioned vector font, set the third parameter to
  8270.  ScaleVectorFont equal to the second parameter.
  8271.  
  8272.  The VF01.C file in Figure 3 shows how the functions discussed so far can be
  8273.  used to display all the available vector fonts in 24-point size. You can run
  8274.  the function in VF01.C by selecting the 24 Point Fonts option from the
  8275.  VECTFONT Display menu. The source code and its results are shown in Figure
  8276.  3.
  8277.  
  8278.  Figure 3:
  8279.  
  8280.  /*----------------------------------------
  8281.     VF01.C -- Display 24-point vector fonts
  8282.     ----------------------------------------*/
  8283.  #define INCL_GPI
  8284.  #include <os2.h>
  8285.  #include <string.h>
  8286.  #include "vectfont.h"
  8287.  
  8288.  VOID Display_24Point (HPS hps, LONG cxClient, LONG cyClient)
  8289.       {
  8290.       static CHAR *szFacename[] = {
  8291.                                   "Courier",      "Courier Italic",
  8292.                                   "Courier Bold", "Courier Bold Italic",
  8293.                                   "Tms Rmn",      "Tms Rmn Italic",
  8294.                                   "Tms Rmn Bold", "Tms Rmn Bold Italic",
  8295.                                   "Helv",         "Helv Italic",
  8296.                                   "Helv Bold",    "Helv Bold Italic"
  8297.                                   } ;
  8298.       static INT  iNumFonts = sizeof szFacename / sizeof szFacename[0] ;
  8299.       FONTMETRICS fm ;
  8300.       INT         iFont ;
  8301.       POINTL      ptl ;
  8302.  
  8303.       ptl.x = cxClient / 8 ;
  8304.       ptl.y = cyClient ;
  8305.  
  8306.       for (iFont = 0 ; iFont < iNumFonts ; iFont++)
  8307.            {
  8308.                                     // Create font, select it and scale
  8309.  
  8310.            CreateVectorFont (hps, LCID_MYFONT, szFacename[iFont]) ;
  8311.            GpiSetCharSet (hps, LCID_MYFONT) ;
  8312.            ScaleVectorFont (hps, 240, 240) ;
  8313.  
  8314.                                     // Get font metrics for scaled font
  8315.  
  8316.            GpiQueryFontMetrics (hps, (LONG) sizeof (FONTMETRICS), &fm) ;
  8317.            ptl.y -= fm.lMaxBaselineExt ;
  8318.  
  8319.                                     // Display the font facename
  8320.  
  8321.            GpiCharStringAt (hps, &ptl, (LONG) strlen (szFacename[iFont]),
  8322.                             szFacename[iFont]) ;
  8323.  
  8324.            GpiCharString (hps, 10L, " - abcdefg") ;
  8325.  
  8326.            GpiSetCharSet (hps, LCID_DEFAULT) ;     // Clean up
  8327.            GpiDeleteSetId (hps, LCID_MYFONT) ;
  8328.            }
  8329.       }
  8330.  
  8331.  Arbitrary Stretching
  8332.  
  8333.  Besides scaling the vector font to a specific point size, you can also scale
  8334.  vector fonts to fit within an arbitrary rectangle. For example, you may want
  8335.  to scale a short text string to fill the client window.
  8336.  
  8337.  The function shown in VF02.C (see Figure 4) shows how this is done. You can
  8338.  run this function by selecting Stretched Font from VECTFONT's menu. The
  8339.  function in VF02.C displays the word "Hello!" in the Tms Rmn Italic font
  8340.  stretched to the size of the client window.
  8341.  
  8342.  The ScaleFontToBox function in VF00.C helps out with this job. This function
  8343.  first calls GpiQueryTextBox to obtain the coordinates of a parallelogram
  8344.  that encompasses the text string. The character box is then scaled based on
  8345.  the size of this text box and the rectangle to which the font must be
  8346.  stretched. The QueryStartPointInTextBox function in VF00.C determines the
  8347.  starting point of the text string (that is, the point at the baseline of the
  8348.  left side of the first character) within this rectangle. This point is used
  8349.  in the GpiCharStringAt function to display the text.
  8350.  
  8351.  Figure 4:
  8352.  
  8353.  /*----------------------------------------------------------
  8354.     VF02.C -- Display vector font stretched to client window
  8355.    ----------------------------------------------------------*/
  8356.  
  8357.  #define INCL_GPI
  8358.  #include <os2.h>
  8359.  #include "vectfont.h"
  8360.  
  8361.  VOID Display_Stretch (HPS hps, LONG cxClient, LONG cyClient)
  8362.       {
  8363.       static CHAR szText[] = "Hello!" ;
  8364.       static LONG cbText = sizeof szText - 1 ;
  8365.       POINTL      ptl ;
  8366.  
  8367.                                // Create font, select, and scale
  8368.  
  8369.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8370.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8371.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  8372.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  8373.  
  8374.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Display text
  8375.  
  8376.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8377.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8378.       }
  8379.  
  8380.  Mirror Images
  8381.  
  8382.  The character box, besides scaling the font to a particular size, can also
  8383.  flip the characters around the horizontal or vertical axis.
  8384.  
  8385.  If the character box height (the cy field of the SIZEF structure) is
  8386.  negative, the characters will be flipped around the baseline and displayed
  8387.  upside down. If the character box width (the cx field) is negative, the
  8388.  individual characters will be flipped around the vertical axis. Moreover,
  8389.  GpiCharStringAt will draw a character string from right to left. It's as
  8390.  though the whole character string is flipped around the vertical line at the
  8391.  left side of the first character.
  8392.  
  8393.  The function in VF03.C (see Figure 5) displays the same string four times,
  8394.  using all possible combinations of negative and positive character box
  8395.  dimensions. You can run this function by selecting Mirrored Font from the
  8396.  VECTFONT menu.
  8397.  
  8398.  Figure 5:
  8399.  
  8400.  /*------------------------------------------------------
  8401.     VF03.C -- Display four strings in mirror reflections
  8402.    ------------------------------------------------------*/
  8403.  
  8404.  #define INCL_GPI
  8405.  #include <os2.h>
  8406.  #include "vectfont.h"
  8407.  
  8408.  VOID Display_Mirror (HPS hps, LONG cxClient, LONG cyClient)
  8409.       {
  8410.       static CHAR szText[] = "Mirror" ;
  8411.       static LONG cbText = sizeof szText - 1 ;
  8412.       INT         i ;
  8413.       POINTL      ptl ;
  8414.       SIZEF       sizfx ;
  8415.                                     // Create font, select and scale
  8416.  
  8417.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8418.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8419.       ScaleFontToBox (hps, cbText, szText, cxClient / 2, cyClient / 2) ;
  8420.  
  8421.       ptl.x = cxClient / 2 ;        // Center of client window
  8422.       ptl.y = cyClient / 2 ;
  8423.  
  8424.       for (i = 0 ; i < 4 ; i++)
  8425.            {
  8426.            GpiQueryCharBox (hps, &sizfx) ;
  8427.  
  8428.            if (i == 1 || i == 3)
  8429.                 sizfx.cx *= -1 ;
  8430.                                     // Negate char box dimensions
  8431.            if (i == 2)
  8432.                 sizfx.cy *= -1 ;
  8433.  
  8434.            GpiSetCharBox (hps, &sizfx) ;
  8435.  
  8436.            GpiCharStringAt (hps, &ptl, cbText, szText) ;
  8437.            }
  8438.       GpiSetCharSet (hps, LCID_DEFAULT) ;          // Clean up
  8439.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8440.  
  8441.       }
  8442.  
  8443.  Transformations
  8444.  
  8445.  Unlike image fonts, vector fonts can be scaled to any size and sheared and
  8446.  rotated to any angle. The matrix transforms described in my article "OS/2
  8447.  Graphics Programming Interface: An Introduction to Coordinate Spaces," MSJ
  8448.  (Vol. 3, No. 4), affect vector fonts in the same way they affect the display
  8449.  of other graphics primitives.
  8450.  
  8451.  In addition, GPI also supports several functions specifically for performing
  8452.  transforms on vector fonts. We've already seen how the GpiSetCharBox
  8453.  function allows font characters to be scaled. The GpiSetCharAngle rotates
  8454.  the font characters and the GpiSetCharShear performs x shear.
  8455.  
  8456.  Character Angle and Rotation
  8457.  
  8458.  By default, the baseline of the vector font characters is parallel to the x
  8459.  axis in world coordinates. You can change this by calling GpiSetCharAngle.
  8460.  This rotates the vector font's characters.
  8461.  
  8462.  The character angle is specified using a GRADIENTL structure, which has two
  8463.  fields named x and y of type LONG. Imagine a line connecting the point (0,0)
  8464.  to the point (x,y) in world coordinates. The baseline of each character is
  8465.  parallel to this line. The direction of the text is the same as the
  8466.  direction from (0,0) to (x,y).
  8467.  
  8468.  You can also think of this in trigonometric terms. The baseline of the text
  8469.  is parallel to a line at angle a measured counterclockwise from the x axis,
  8470.  where:
  8471.  
  8472.  a = arctan (y/x)
  8473.  
  8474.  and y and x are the two fields of the GRADIENTL structure.
  8475.  
  8476.  The absolute magnitudes of y and x are not important. What's important are
  8477.  the relative magnitudes and signs. The signs of x and y determine the
  8478.  direction of the text string as indicated in the following table:
  8479.  
  8480.  x    y    Direction
  8481.  
  8482.  +    +    To upper right
  8483.  -     +    To upper left
  8484.  -    -    To lower left
  8485.      +    -    To lower right
  8486.  
  8487.  The function in VF04.C (see Figure 6) uses the GpiSetCharAngle function to
  8488.  display eight text strings at 45 degree increments around the center of the
  8489.  client window. Each string displays the fields of the GRADIENTL structure
  8490.  that is used in the GpiSetCharAngle call.
  8491.  
  8492.  In this example, the text strings begin with a blank character so as not to
  8493.  make a mess of overlapping characters in the center of the client window.
  8494.  The character angle does not affect the interpretation of the starting
  8495.  position of the string specified in the GpiCharStringAt function. If you
  8496.  move your head so that a particular string is seen as running left to right,
  8497.  the starting position still refers to the point at the baseline of the left
  8498.  side of the first character.
  8499.  
  8500.  You can make the characters in a text string follow a curved path by
  8501.  individually calculating the starting position angle of each character and
  8502.  displaying the characters one at a time. This is what is done in VF05.C (see
  8503.  Figure 7) to display "Hello, World!" around the perimeter of a circle.
  8504.  
  8505.  The text string is scaled based on the circumference of a circle that is
  8506.  positioned in the center of the window and has a diameter half the width or
  8507.  height (whichever is less) of the window. The GpiQueryWidthTable function is
  8508.  used to obtain the width of individual characters and then space them around
  8509.  the circle.
  8510.  
  8511.  Figure 6:
  8512.  
  8513.  /*------------------------------------------
  8514.     VF04.C -- Display eight character angles
  8515.    ------------------------------------------*/
  8516.  
  8517.  #define INCL_GPI
  8518.  #include <os2.h>
  8519.  #include <stdio.h>
  8520.  #include "vectfont.h"
  8521.  
  8522.  VOID Display_CharAngle (HPS hps, LONG cxClient, LONG cyClient)
  8523.       {
  8524.       static GRADIENTL agradl[8] = { 100,    0,  100,  100,
  8525.                                        0,  100, -100,  100,
  8526.                                     -100,    0, -100, -100,
  8527.                                        0, -100,  100, -100 } ;
  8528.       CHAR             szBuffer[40] ;
  8529.       INT              iIndex ;
  8530.       POINTL           ptl ;
  8531.  
  8532.                                // Create Helvetica font
  8533.  
  8534.       CreateVectorFont (hps, LCID_MYFONT, "Helv") ;
  8535.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8536.       ScaleVectorFont (hps, 200, 200) ;
  8537.  
  8538.       ptl.x = cxClient / 2 ;   // Center of client window
  8539.       ptl.y = cyClient / 2 ;
  8540.  
  8541.       for (iIndex = 0 ; iIndex < 8 ; iIndex++)
  8542.            {
  8543.            GpiSetCharAngle (hps, agradl + iIndex) ;     // Char angle
  8544.  
  8545.            GpiCharStringAt (hps, &ptl,
  8546.                 (LONG) sprintf (szBuffer, " Character Angle (%ld,%ld)",
  8547.                                 agradl[iIndex].x, agradl[iIndex].y),
  8548.                 szBuffer) ;
  8549.            }
  8550.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8551.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8552.       }
  8553.  
  8554.  Character Shear
  8555.  
  8556.  It is easy to confuse the character angle with the character shear. Let's
  8557.  look at the difference. The character angle refers to the orientation of the
  8558.  baseline. As you can see in Figures 6 and 7, text displayed with various
  8559.  character angles is rotated but otherwise not distorted in any way.
  8560.  
  8561.  The character shear affects the appearance of the characters themselves
  8562.  apart from any rotation. Character shear by itself bends characters to the
  8563.  left or right, but the bottom of each character remains parallel to the x
  8564.  axis. You can use character shear to create oblique (sometimes mistakenly
  8565.  called italic) versions of a font.
  8566.  
  8567.  To set character shear you call the GpiSetCharShear function. This function
  8568.  requires a pointer to a structure of type POINTL, which has two fields named
  8569.  x and y. Imagine a line drawn from (0,0) to the point (x,y) in world
  8570.  coordinates. The left and right sides of each character are parallel to this
  8571.  line.
  8572.  
  8573.  The function shown in in VF06.C (see Figure 8) displays seven text strings
  8574.  using different character shears. You can run this function by selecting
  8575.  Character Shear from the VECTFONT menu. Each string displays the x and y
  8576.  values used in the POINTL structure to set the character shear.
  8577.  
  8578.  The character shear is governed by the relative magnitudes and signs of the
  8579.  x and y fields of the POINTL structure. If the signs of both fields are the
  8580.  same, then the characters tilt to the right; if they are different, the
  8581.  characters tilt to the left. The character shear does not flip the
  8582.  characters upside down. For example, character shear using the point
  8583.  (100,100) has the same effect as (-100,-100).
  8584.  
  8585.  The angle of the left and right sides of the characters from the y axis is
  8586.  sometimes called the shear angle. In theory, the shear angle can range to
  8587.  just above -180 degrees (infinite left shear) to just under +180 degrees
  8588.  (infinite right shear) and is equal to
  8589.  
  8590.  a = arctan (x/y)
  8591.  
  8592.  where x and y are the two fields of the POINTL structure.
  8593.  
  8594.  When you set a nondefault character shear, the GpiQueryTextBox function
  8595.  returns an array of points that define a parallelogram rather than a
  8596.  rectangle. However, the top and bottom sides of this text box are the same
  8597.  width as for a nonsheared text string, and the distance between the top and
  8598.  bottom sides also remains the same.
  8599.  
  8600.  You can use character shear to draw an oblique shadow of a text string. The
  8601.  function in VF07.C (see Figure 9) colors the background of the client window
  8602.  blue and displays the text string Shadow twice.
  8603.  
  8604.  The first call to GpiCharStringAt displays the shadow. This is drawn in dark
  8605.  blue using a positive character shear. The second call to GpiCharStringAt
  8606.  draws the characters upright in red with a slightly smaller character box
  8607.  height. You can run this function by selecting Font with Shadow from the
  8608.  VECTFONT menu.
  8609.  
  8610.  Figure 7:
  8611.  
  8612.  /*--------------------------------------------
  8613.     VF05.C -- Display "Hello, World" in circle
  8614.    --------------------------------------------*/
  8615.  
  8616.  #define INCL_GPI
  8617.  #include <os2.h>
  8618.  #include <math.h>
  8619.  #include <stdlib.h>
  8620.  #include "vectfont.h"
  8621.  
  8622.  VOID Display_Rotate (HPS hps, LONG cxClient, LONG cyClient)
  8623.       {
  8624.       static CHAR szText[] = "Hello, world! " ;
  8625.       static LONG cbText = sizeof szText - 1L ;
  8626.       static LONG alWidthTable[256] ;
  8627.       double      ang, angCharWidth, angChar ;
  8628.       FONTMETRICS fm ;
  8629.       GRADIENTL   gradl ;
  8630.       INT         iChar ;
  8631.       LONG        lCircum, lRadius, lTotWidth, lCharRadius, cyChar ;
  8632.       POINTL      ptl ;
  8633.  
  8634.                           // Create the font and get font metrics
  8635.  
  8636.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
  8637.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8638.  
  8639.       GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  8640.  
  8641.                           // Find circle dimensions and scale font
  8642.  
  8643.       lRadius = min (cxClient / 4, cyClient / 4) ;
  8644.       lCircum = (LONG) (2 * PI * lRadius) ;
  8645.       cyChar  = fm.lMaxBaselineExt * lRadius / fm.lMaxAscender ;
  8646.  
  8647.       ScaleFontToBox (hps, cbText, szText, lCircum, cyChar) ;
  8648.  
  8649.                           // Obtain width table and total width
  8650.  
  8651.       GpiQueryWidthTable (hps, 0L, 256L, alWidthTable) ;
  8652.  
  8653.       for (lTotWidth = 0, iChar = 0 ; iChar < (INT) cbText ; iChar ++)
  8654.            lTotWidth += alWidthTable [szText [iChar]] ;
  8655.  
  8656.       ang = PI / 2 ;      // Initial angle for first character
  8657.  
  8658.       for (iChar = 0 ; iChar < (INT) cbText ; iChar++)
  8659.            {
  8660.                           // Set character angle
  8661.  
  8662.            angCharWidth = 2 * PI * alWidthTable [szText [iChar]] / lTotWidth
  8663.  ;
  8664.  
  8665.            gradl.x = (LONG) (lRadius * cos (ang - angCharWidth / 2 - PI / 2))
  8666.  ;
  8667.            gradl.y = (LONG) (lRadius * sin (ang - angCharWidth / 2 - PI / 2))
  8668.  ;
  8669.  
  8670.            GpiSetCharAngle (hps, &gradl) ;
  8671.  
  8672.                           // Find position for character and display it
  8673.  
  8674.            angChar = atan2 ((double) alWidthTable [szText [iChar]] / 2,
  8675.                             (double) lRadius) ;
  8676.  
  8677.            lCharRadius = (LONG) (lRadius / cos (angChar)) ;
  8678.            angChar += ang - angCharWidth / 2 ;
  8679.  
  8680.            ptl.x = (LONG) (cxClient / 2 + lCharRadius * cos (angChar)) ;
  8681.            ptl.y = (LONG) (cyClient / 2 + lCharRadius * sin (angChar)) ;
  8682.  
  8683.            GpiCharStringAt (hps, &ptl, 1L, szText + iChar) ;
  8684.  
  8685.            ang -= angCharWidth ;
  8686.            }
  8687.       GpiSetCharSet (hps, LCID_DEFAULT) ;          // Clean up
  8688.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8689.       }
  8690.  
  8691.  Figure 8:
  8692.  
  8693.  /*----------------------------------------------------------
  8694.     VF06.C -- Display seven different character shear angles
  8695.    ----------------------------------------------------------*/
  8696.  
  8697.  #define INCL_GPI
  8698.  #include <os2.h>
  8699.  #include <stdio.h>
  8700.  #include "vectfont.h"
  8701.  
  8702.  VOID Display_CharShear (HPS hps, LONG cxClient, LONG cyClient)
  8703.       {
  8704.       static POINTL aptlShear[7] = { -100,  41, -100, 100,
  8705.                                       -41, 100,    0, 100,
  8706.                                        41, 100,  100, 100,
  8707.                                       100,  41 } ;
  8708.       CHAR          szBuffer[40] ;
  8709.       FONTMETRICS   fm ;
  8710.       INT           iIndex ;
  8711.       POINTL        ptl ;
  8712.  
  8713.                           // Create and scale Helvetica font
  8714.  
  8715.       CreateVectorFont (hps, LCID_MYFONT, "Helv") ;
  8716.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8717.       ScaleVectorFont (hps, 480, 480) ;
  8718.  
  8719.                           // Get font metrics for scaled font
  8720.  
  8721.       GpiQueryFontMetrics (hps, (LONG) sizeof (FONTMETRICS), &fm) ;
  8722.  
  8723.       ptl.x = cxClient / 8 ;
  8724.       ptl.y = cyClient ;
  8725.  
  8726.       for (iIndex = 0 ; iIndex < 7 ; iIndex++)
  8727.            {
  8728.            GpiSetCharShear (hps, aptlShear + iIndex) ;  // Char shear
  8729.  
  8730.            ptl.y -= fm.lMaxBaselineExt ;
  8731.  
  8732.            GpiCharStringAt (hps, &ptl,
  8733.                 (LONG) sprintf (szBuffer, "Character Shear (%ld,%ld)",
  8734.                           aptlShear[iIndex].x, aptlShear[iIndex].y),
  8735.                 szBuffer) ;
  8736.            }
  8737.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8738.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8739.       }
  8740.  
  8741.  Figure 9:
  8742.  
  8743.  /*--------------------------------------------------
  8744.     VF07.C -- Display characters with sheared shadow
  8745.    --------------------------------------------------*/
  8746.  
  8747.  #define INCL_GPI
  8748.  #include <os2.h>
  8749.  #include "vectfont.h"
  8750.  
  8751.  VOID Display_Shadow (HPS hps, LONG cxClient, LONG cyClient)
  8752.       {
  8753.       static CHAR szText[] = "Shadow" ;
  8754.       static LONG cbText = sizeof szText - 1 ;
  8755.       POINTL      ptl, ptlShear ;
  8756.       SIZEF       sizfx ;
  8757.  
  8758.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8759.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8760.       ScaleFontToBox (hps, cbText, szText, 3 * cxClient / 4, cyClient) ;
  8761.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  8762.  
  8763.       ColorClient (hps, cxClient, cyClient, CLR_BLUE) ;
  8764.  
  8765.       GpiSavePS (hps) ;
  8766.  
  8767.       ptlShear.x = 200 ;                             // Set char shear
  8768.       ptlShear.y = 100 ;
  8769.       GpiSetCharShear (hps, &ptlShear) ;
  8770.  
  8771.       GpiQueryCharBox (hps, &sizfx) ;
  8772.       sizfx.cy += sizfx.cy / 4 ;                     // Set char box
  8773.       GpiSetCharBox (hps, &sizfx) ;
  8774.  
  8775.       GpiSetColor (hps, CLR_DARKBLUE) ;
  8776.       GpiCharStringAt (hps, &ptl, cbText, szText) ;  // Display shadow
  8777.  
  8778.       GpiRestorePS (hps, -1L) ;
  8779.  
  8780.       GpiSetColor (hps, CLR_RED) ;
  8781.       GpiCharStringAt (hps, &ptl, cbText, szText) ;  // Display text
  8782.  
  8783.       GpiSetCharSet (hps, LCID_DEFAULT) ;            // Clean up
  8784.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8785.       }
  8786.  
  8787.  A Primer on Paths
  8788.  
  8789.  To explore more capabilities of vector fonts, it's necessary to become
  8790.  familiar with the GPI path, which is similar to a PostScript path. In GPI,
  8791.  you create a path by calling line drawing functions between calls to the
  8792.  GpiBeginPath and GpiEndPath functions:
  8793.  
  8794.  GpiBeginPath(hps, idPath);
  8795.      <... call line drawing functions ... >
  8796.  GpiEndPath (hps) ;
  8797.  
  8798.  This is called a path bracket. In OS/2 1.1, idPath must be set equal to 1L.
  8799.  The functions that are valid within a path bracket are listed in the
  8800.  documentation of the Presentation Manager functions.
  8801.  
  8802.  The functions you call within the path bracket do not draw anything.
  8803.  Instead, the lines that make up the path are retained by the system. Often
  8804.  the lines you draw in a path will enclose areas, but they don't have to.
  8805.  
  8806.  After the GpiEndPath call, you can do one of three things with the path
  8807.  you've created:
  8808.  
  8809.  ■    Call GpiStrokePath to draw the lines that comprise the path. These
  8810.  lines are drawn using the geometric line width, joins, and ends (discussed
  8811.  shortly).
  8812.  
  8813.  ■    Call GpiFillPath to fill enclosed areas defined by the path. Any open
  8814.  areas are automatically closed. The area is filled with the current pattern.
  8815.  
  8816.  ■    Call GpiSetClipPath to make the enclosed areas of the path a clipping
  8817.  area. Any open areas are automatically closed. Subsequent GPI calls will
  8818.  only display output within the enclosed area defined by the path.
  8819.  
  8820.  Each of these three functions causes the path to be deleted. Prior to
  8821.  calling any of these three functions, you can call GpiModifyPath, which I'll
  8822.  describe toward the end of this article.
  8823.  
  8824.  Normally, GpiCharStringAt and the other text output functions are not valid
  8825.  in a path bracket. The exception is when a vector font is selected in the
  8826.  presentation space. When called from within a path bracket, GpiCharStringAt
  8827.  does not draw the text string. Instead, the outlines of the characters
  8828.  become part of the path.
  8829.  
  8830.  Paths opens up a whole collection of PostScript-like stylistic techniques
  8831.  that you can use with vector fonts.
  8832.  
  8833.  Hollow Characters
  8834.  
  8835.  Let's begin by calling GpiCharStringAt in a path bracket and then use
  8836.  GpiStrokePath to draw the lines of the path. GpiStrokePath has the following
  8837.  syntax:
  8838.  
  8839.  GpiStrokePath(hps, idPath, 0L);
  8840.  
  8841.  In the initial version of the Presentation Manager, the last parameter of
  8842.  the function must be set to 0L. When used to stroke a path created by
  8843.  calling GpiCharStringAt, only the outline is drawn and not the interiors.
  8844.  This creates hollow characters.
  8845.  
  8846.  The function in VF08.C (see Figure 10) uses this technique to display the
  8847.  outline of the characters in the text string Hollow. You can run this
  8848.  function by selecting Hollow Font from the VECTFONT menu.
  8849.  
  8850.  You may want to display characters in one color with an outline of another
  8851.  color. In this case, you must call GpiCharStringAt twice, first to draw the
  8852.  interior in a specific color and second, in a path bracket followed by a
  8853.  GpiStrokePath call to draw the outline. The function in VF09.C (see Figure
  8854.  11) does something like this to draw text with a drop shadow.
  8855.  
  8856.  The shadow is drawn first using a normal GpiCharStringAt call. Another
  8857.  GpiCharStringAt function draws the text again in the current window
  8858.  background color at a 1/6-inch offset to the first string. This is
  8859.  surrounded by an outline created by a third GpiCharStringAt call in a path
  8860.  bracket followed by GpiStrokePath.
  8861.  
  8862.  Quite similar to this is the creation of characters that look like solid
  8863.  blocks, as shown in the function in VF10.C ( see Figure12). Eighteen
  8864.  character strings are drawn at 1-point offsets using CLR_DARKGREEN. This is
  8865.  capped by the character string drawn again in CLR_GREEN and the border in
  8866.  CLR_DARKGREEN.
  8867.  
  8868.  Figure 10:
  8869.  
  8870.  /*-----------------------
  8871.     VF08.C -- Hollow font
  8872.    -----------------------*/
  8873.  
  8874.  #define INCL_GPI
  8875.  #include <os2.h>
  8876.  #include "vectfont.h"
  8877.  
  8878.  VOID Display_Hollow (HPS hps, LONG cxClient, LONG cyClient)
  8879.       {
  8880.       static CHAR szText[] = "Hollow" ;
  8881.       static LONG cbText = sizeof szText - 1 ;
  8882.       POINTL      ptl ;
  8883.  
  8884.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8885.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8886.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  8887.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  8888.  
  8889.       GpiBeginPath (hps, ID_PATH) ;
  8890.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text in path
  8891.       GpiEndPath (hps) ;
  8892.  
  8893.       GpiStrokePath (hps, ID_PATH, 0L) ;                // Stroke path
  8894.  
  8895.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8896.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8897.  
  8898.  Figure 11:
  8899.  
  8900.  *---------------------------------
  8901.     VF09.C -- Font with Drop Shadow
  8902.    ---------------------------------*/
  8903.  
  8904.  #define INCL_GPI
  8905.  #include <os2.h>
  8906.  #include "vectfont.h"
  8907.  
  8908.  VOID Display_DropShadow (HPS hps, LONG cxClient, LONG cyClient)
  8909.       {
  8910.       static CHAR szText[] = "Hello!" ;
  8911.       static LONG cbText = sizeof szText - 1 ;
  8912.       POINTL      ptl ;
  8913.  
  8914.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8915.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8916.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  8917.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  8918.  
  8919.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Shadow
  8920.  
  8921.       ptl.x -= 12 ;       // 1/6 inch
  8922.       ptl.y += 12 ;
  8923.  
  8924.       GpiSetColor (hps, CLR_BACKGROUND) ;
  8925.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
  8926.  
  8927.       GpiBeginPath (hps, ID_PATH) ;
  8928.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Outline
  8929.       GpiEndPath (hps) ;
  8930.  
  8931.       GpiSetColor (hps, CLR_NEUTRAL) ;
  8932.       GpiStrokePath (hps, ID_PATH, 0L) ;
  8933.  
  8934.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8935.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8936.       }
  8937.  
  8938.  Figure 12:
  8939.  
  8940.  /*----------------------------
  8941.     VF10.C -- Solid block font
  8942.    ----------------------------*/
  8943.  
  8944.  #define INCL_GPI
  8945.  #include <os2.h>
  8946.  #include "vectfont.h"
  8947.  
  8948.  VOID Display_Block (HPS hps, LONG cxClient, LONG cyClient)
  8949.       {
  8950.       static CHAR szText[] = " Block " ;
  8951.       static LONG cbText = sizeof szText - 1 ;
  8952.       INT         i ;
  8953.       POINTL      ptl ;
  8954.  
  8955.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  8956.       GpiSetCharSet (hps, LCID_MYFONT) ;
  8957.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  8958.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  8959.  
  8960.       ColorClient (hps, cxClient, cyClient, CLR_WHITE) ;
  8961.       GpiSetColor (hps, CLR_DARKGREEN) ;
  8962.  
  8963.       for (i = 0 ; i < 18 ; i++)
  8964.            {
  8965.            GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Block
  8966.  
  8967.            ptl.x -= 1 ;
  8968.            ptl.y -= 1 ;
  8969.            }
  8970.  
  8971.       GpiSetColor (hps, CLR_GREEN) ;
  8972.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
  8973.  
  8974.       GpiBeginPath (hps, ID_PATH) ;
  8975.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Outline
  8976.       GpiEndPath (hps) ;
  8977.  
  8978.       GpiSetColor (hps, CLR_DARKGREEN) ;
  8979.       GpiStrokePath (hps, ID_PATH, 0L) ;
  8980.  
  8981.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  8982.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  8983.       }
  8984.  
  8985.  
  8986.  Filling the Path
  8987.  
  8988.  When first introducing paths, I mentioned that you can do one of three
  8989.  things with a path: stroke it, fill it, or use it for clipping. Let's move
  8990.  on to the second; filling the path.
  8991.  
  8992.  To fill a path, you call:
  8993.  
  8994.  GpiFillPath(hps, idPath, lOption);
  8995.  
  8996.  This fills the path with the current pattern. Any open areas of the path are
  8997.  automatically closed before being filled. The lOption parameter can be
  8998.  either FPATH_ALTERNATE or FPATH_WINDING to fill the path using alternate or
  8999.  winding modes. For vector fonts, FPATH_WINDING causes the interiors of some
  9000.  letters (such as O) to be filled. You'll probably want to use
  9001.  FPATH_ALTERNATE instead.
  9002.  
  9003.  If the current area filling pattern is PATSYM_SOLID, the code
  9004.  
  9005.  GpiBeginPath(hps, idPath);
  9006.  GpiCharStringAt(hps, &ptl,
  9007.               cch, szText);
  9008.  GpiEndPath(hps);
  9009.  GpiFillPath(hps,idPath,
  9010.            FPATH_ALTERNATE);
  9011.  
  9012.  does roughly the same thing with a vector font as does a GpiCharStringAt by
  9013.  itself. When using GpiFillPath you will want to set a pattern other than
  9014.  PATSYM_SOLID. (A bug in OS/2 1.1 causes the current pattern to be reset to
  9015.  PATSYM_SOLID during a path bracket in which GpiCharStringAt is called. You
  9016.  can get around this bug by calling GpiSetPattern after you end the path.)
  9017.  
  9018.  The function in VF11.C (see Figure 13) uses GpiFillPath to display the text
  9019.  string Fade eight times filled with the eight GPI shading patterns
  9020.  (PATSYM_DENSE1 through PATSYM_DENSE8) and then finishes by calling
  9021.  GpiCharStringAt outside of a path bracket. You can run this function by
  9022.  selecting Fading Font from the VECTFONT menu.
  9023.  
  9024.  Figure 13:
  9025.  
  9026.  /*------------------------------------------------------
  9027.     VF11.C -- Fading font with various pattern densities
  9028.    ------------------------------------------------------*/
  9029.  
  9030.  #define INCL_GPI
  9031.  #include <os2.h>
  9032.  #include "vectfont.h"
  9033.  
  9034.  VOID Display_Fade (HPS hps, LONG cxClient, LONG cyClient)
  9035.       {
  9036.       static CHAR szText[] = "Fade" ;
  9037.       static LONG cbText = sizeof szText - 1 ;
  9038.       LONG        lPattern ;
  9039.       POINTL      ptl ;
  9040.  
  9041.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  9042.       GpiSetCharSet (hps, LCID_MYFONT) ;
  9043.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  9044.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  9045.  
  9046.       GpiSetBackMix (hps, BM_OVERPAINT) ;
  9047.  
  9048.       for (lPattern = 8 ; lPattern >= 1 ; lPattern--)
  9049.            {
  9050.            GpiBeginPath (hps, ID_PATH) ;
  9051.            GpiCharStringAt (hps, &ptl, cbText, szText) ;  // Text out
  9052.            GpiEndPath (hps) ;
  9053.  
  9054.            GpiSetPattern (hps, lPattern) ;
  9055.            GpiFillPath (hps, ID_PATH, FPATH_ALTERNATE) ;  // Fill path
  9056.  
  9057.            ptl.x += 2 ;
  9058.            ptl.y -= 2 ;
  9059.            }
  9060.  
  9061.       GpiSetPattern (hps, PATSYM_SOLID) ;
  9062.       GpiSetBackMix (hps, BM_LEAVEALONE) ;
  9063.       GpiCharStringAt (hps, &ptl, cbText, szText) ;       // Solid
  9064.  
  9065.       GpiSetCharSet (hps, LCID_DEFAULT) ;                 // Clean up
  9066.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  9067.       }
  9068.  
  9069.  Geometrically Thick Lines
  9070.  
  9071.  At first, it may seem as though there is no difference between drawing a
  9072.  line normally, like this:
  9073.  
  9074.  GpiMove(hps, &ptlBeg);
  9075.  GpiLine(hps, &ptlEnd);
  9076.  
  9077.  and calling these same two functions within a path bracket and then stroking
  9078.  the path, like this:
  9079.  
  9080.  GpiBeginPath(hps, idPath);
  9081.  GpiMove(hps, &ptlBeg);
  9082.  GpiLine(hps, &ptlEnd);
  9083.  GpiEndPath(hps);
  9084.  GpiStrokePath(hps, idPath, 0);
  9085.  
  9086.  There are, in fact, some very significant differences.
  9087.  
  9088.  First, the line drawn with the normal GpiLine function has what is called a
  9089.  cosmetic line width. The default width of the line is based on the
  9090.  resolution of the output device. It is a device-dependent width for a normal
  9091.  line. The width of the line does not change when you use matrix transforms
  9092.  to set different scaling factors in the coordinate space. Although GPI
  9093.  provides a function called GpiSetLineWidth to change the cosmetic line
  9094.  width, this function is not implemented in MS (R) OS/2 Version 1.1.
  9095.  
  9096.  But a line drawn by stroking a path has a geometric line width. This is a
  9097.  line width (in world coordinates) that you set with the GpiSetLineWidthGeom
  9098.  function. Because this line width is specified in world coordinates, it is
  9099.  affected by any scaling that you set using matrix transforms.
  9100.  
  9101.  Second, a line drawn with GpiLine can have different line types that you set
  9102.  with the GpiSetLineType function. These line types are various combinations
  9103.  of dots and dashes. The line is drawn with the current line color and the
  9104.  current line mix.
  9105.  
  9106.  But a line drawn by stroking a path does not use the line type. The line is
  9107.  instead treated as an area that follows the path of the line but which has a
  9108.  geometric width. This area is filled with the pattern that you set with
  9109.  GpiSetPattern, and is colored with the current pattern foreground and
  9110.  background color, and the current pattern foreground and background mix.
  9111.  
  9112.  Third, a line drawn by stroking the path can have various types of line
  9113.  joins and ends. By calling GpiSetLineJoin you can specify that lines meet
  9114.  with a rounded, square, or miter join. GpiSetLineEnd lets you specify
  9115.  rounded, square, or flat ends to the lines.
  9116.  
  9117.  The function in the VF12.C file (see Figure 14) demonstrates the use of
  9118.  geometrically thick lines filled with patterns to give the letters a kind of
  9119.  neon look.
  9120.  
  9121.  You can run this function by selecting Neon Effect from the VECTFONT menu.
  9122.  The function strokes the path using various geometric line widths filled
  9123.  with a PATSYM_HALFTONE pattern and several colors. The outline of the font
  9124.  is white, but it is surrounded with a halo of red.
  9125.  
  9126.  A better effect could be achieved on devices capable of more than 16 colors.
  9127.  In this case, you can use a solid pattern but color each stroke with a
  9128.  different shade of red.
  9129.  
  9130.  Figure 14:
  9131.  
  9132.  /*-----------------------------------------------------
  9133.     VF12.C -- Neon font using geometricall thick lines
  9134.    -----------------------------------------------------*/
  9135.  
  9136.  #define INCL_GPI
  9137.  #include <os2.h>
  9138.  #include "vectfont.h"
  9139.  
  9140.  VOID Display_Neon (HPS hps, LONG cxClient, LONG cyClient)
  9141.       {
  9142.       static CHAR szText[] = " Neon " ;
  9143.       static LONG cbText = sizeof szText - 1 ;
  9144.       static LONG lForeColor[] = { CLR_DARKRED, CLR_DARKRED, CLR_RED,
  9145.                                    CLR_RED,     CLR_WHITE,   CLR_WHITE };
  9146.       static LONG lBackColor[] = { CLR_BLACK,   CLR_DARKRED, CLR_DARKRED,
  9147.                                    CLR_RED,     CLR_RED,     CLR_WHITE };
  9148.       static LONG lWidth[] = { 34, 28, 22, 16, 10, 4 } ;
  9149.  
  9150.       INT         iIndex ;
  9151.       POINTL      ptl ;
  9152.  
  9153.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn Italic") ;
  9154.       GpiSetCharSet (hps, LCID_MYFONT) ;
  9155.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  9156.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  9157.  
  9158.       ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;
  9159.  
  9160.       for (iIndex = 0 ; iIndex < 6 ; iIndex++)
  9161.            {
  9162.            GpiBeginPath (hps, ID_PATH) ;
  9163.            GpiCharStringAt (hps, &ptl, cbText, szText) ;   // Text out
  9164.            GpiEndPath (hps) ;
  9165.  
  9166.            GpiSetColor (hps, lForeColor[iIndex]) ;
  9167.            GpiSetBackColor (hps, lBackColor[iIndex]) ;
  9168.            GpiSetBackMix (hps, BM_OVERPAINT) ;
  9169.            GpiSetPattern (hps, PATSYM_HALFTONE) ;
  9170.            GpiSetLineWidthGeom (hps, lWidth[iIndex]) ;
  9171.  
  9172.            GpiStrokePath (hps, ID_PATH, 0L) ;           // Stroke path
  9173.            }
  9174.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  9175.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  9176.       }
  9177.  
  9178.  Clipping to the Text Characters
  9179.  
  9180.  The third option after creating a path is to call GpiSetClipPath:
  9181.  
  9182.  GpiSetClipPath(hps, idPath, lOption);
  9183.  
  9184.  The lOption parameter can be either SCP_RESET (which equals 0L, so it's the
  9185.  default) or SCP_AND. The SCP_RESET option causes the clipping path to be
  9186.  reset so that no clipping occurs. The SCP_AND option sets the new clipping
  9187.  path to the intersection of the old clipping path and the path you've just
  9188.  defined in a path bracket. Any open areas in the path are automatically
  9189.  closed.
  9190.  
  9191.  You can combine the SCP_AND option with either SCP_ALTERNATE (the default)
  9192.  or SCP_WINDING. As with GpiFillPath, you'll probably want to use alternate
  9193.  mode when working with paths created from vector fonts.
  9194.  
  9195.  The function in VF13.C (see Figure 15) calls GpiCharStringAt with the text
  9196.  string WOW within a path bracket. This is followed by a call to
  9197.  GpiSetClipPath. The clipping path is now the interior of the letters. The
  9198.  function draws a series of colored lines emanating from the center of the
  9199.  client window.
  9200.  
  9201.  The function in VF14.C (not shown here) uses a similar technique but draws a
  9202.  series of areas defined by splines.
  9203.  
  9204.  Figure 15:
  9205.  
  9206.  /*--------------------------
  9207.     VF13.C -- Clipped Spokes
  9208.    --------------------------*/
  9209.  
  9210.  #define INCL_GPI
  9211.  #include <os2.h>
  9212.  #include <math.h>
  9213.  #include "vectfont.h"
  9214.  
  9215.  VOID Display_Spokes (HPS hps, LONG cxClient, LONG cyClient)
  9216.       {
  9217.       static CHAR szText[] = "WOW" ;
  9218.       static LONG cbText = sizeof szText - 1 ;
  9219.       static LONG lColors[] = { CLR_BLUE, CLR_GREEN, CLR_CYAN,
  9220.                                 CLR_RED,  CLR_PINK,  CLR_YELLOW,
  9221.                                 CLR_WHITE } ;
  9222.       double      dMaxRadius ;
  9223.       INT         i, iNumColors = sizeof lColors / sizeof lColors[0] ;
  9224.       POINTL      ptl ;
  9225.  
  9226.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
  9227.       GpiSetCharSet (hps, LCID_MYFONT) ;
  9228.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  9229.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  9230.  
  9231.       ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;
  9232.  
  9233.       GpiBeginPath (hps, ID_PATH) ;
  9234.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
  9235.       GpiEndPath (hps) ;
  9236.  
  9237.       GpiSetClipPath (hps, ID_PATH, SCP_AND | SCP_ALTERNATE) ;
  9238.  
  9239.       dMaxRadius = sqrt (pow (cxClient / 2.0, 2.0) +
  9240.                          pow (cyClient / 2.0, 2.0)) ;
  9241.                                                         // Draw spokes
  9242.       for (i = 0 ; i < 360 ; i++)
  9243.            {
  9244.            GpiSetColor (hps, lColors[i % iNumColors]) ;
  9245.  
  9246.            ptl.x = cxClient / 2 ;
  9247.            ptl.y = cyClient / 2 ;
  9248.            GpiMove (hps, &ptl) ;
  9249.  
  9250.            ptl.x += (LONG) (dMaxRadius * cos (i * 6.28 / 360)) ;
  9251.            ptl.y += (LONG) (dMaxRadius * sin (i * 6.28 / 360)) ;
  9252.            GpiLine (hps, &ptl) ;
  9253.            }
  9254.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  9255.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  9256.       }
  9257.  
  9258.  Modifying the Path
  9259.  
  9260.  Between the call to GpiEndPath to end the path and the call to
  9261.  GpiStrokePath, GpiFillPath, or GpiSetClipPath, you can call GpiModifyPath.
  9262.  This function uses the current geometric line width, join, and end to
  9263.  convert every line in the path to a new line that encloses an area around
  9264.  the old line. For example, suppose that the path contained a single straight
  9265.  line. After GpiModifyPath the path would contain a closed line in the shape
  9266.  of a hot dog. The width of this hot dog is the geometric line width. The
  9267.  ends of the hot dog could be round, square, or flat, depending on the
  9268.  current line end attribute.
  9269.  
  9270.  Following the creation of a path, these two functions in succession:
  9271.  
  9272.  GpiModifyPath(hps, ID_PATH, MPATH_STROKE);
  9273.  GpiFillPath(hps, ID_PATH,
  9274.              FPATH_WINDING) ;
  9275.  
  9276.  are usually equivalent to:
  9277.  
  9278.  GpiStrokePath(hps, ID_PATH, 0L);
  9279.  
  9280.  GpiModifyPath and GpiStrokePath are the only two functions that use the
  9281.  geometric line width, joins, and ends.
  9282.  
  9283.  In theory, you can call GpiStrokePath after GpiModifyPath, like this:
  9284.  
  9285.  GpiModifyPath(hps, ID_PATH,
  9286.                MPATH_STROKE);
  9287.  GpiStrokePath(hps, ID_PATH,
  9288.                0L);
  9289.  
  9290.  This should do something, and it should be rather interesting, but GPI
  9291.  usually reports that it can't create the path because it's too complex.
  9292.  
  9293.  Instead, let's look at GpiModifyPath followed by GpiSetClipPath. The
  9294.  function in VF15.C (see Figure 16) is almost the same as the one in VF13.C
  9295.  (see Figure 15) except that it sets the geometric line width to 6 (1/12
  9296.  inch) and calls GpiModifyPath before calling GpiSetClipPath.
  9297.  
  9298.  Note that the colored lines are clipped not to the interior of the
  9299.  characters but to their original outlines. By the use of GpiModifyPath, the
  9300.  outlines of the characters have themselves been made into a path that is
  9301.  1/12 inch wide. This is the path that is used for clipping.
  9302.  
  9303.  Figure 16:
  9304.  
  9305.  /*--------------------------
  9306.     VF15.C -- Clipped Spokes
  9307.    --------------------------*/
  9308.  
  9309.  #define INCL_GPI
  9310.  #include <os2.h>
  9311.  #include <math.h>
  9312.  #include "vectfont.h"
  9313.  
  9314.  VOID Display_ModSpokes (HPS hps, LONG cxClient, LONG cyClient)
  9315.       {
  9316.       static CHAR szText[] = "WOW" ;
  9317.       static LONG cbText = sizeof szText - 1 ;
  9318.       static LONG lColors[] = { CLR_BLUE, CLR_GREEN, CLR_CYAN,
  9319.                                 CLR_RED,  CLR_PINK,  CLR_YELLOW,
  9320.                                 CLR_WHITE } ;
  9321.       double      dMaxRadius ;
  9322.       INT         i, iNumColors = sizeof lColors / sizeof lColors[0] ;
  9323.       POINTL      ptl ;
  9324.  
  9325.       CreateVectorFont (hps, LCID_MYFONT, "Tms Rmn") ;
  9326.       GpiSetCharSet (hps, LCID_MYFONT) ;
  9327.       ScaleFontToBox (hps, cbText, szText, cxClient, cyClient) ;
  9328.       QueryStartPointInTextBox (hps, cbText, szText, &ptl) ;
  9329.  
  9330.       ColorClient (hps, cxClient, cyClient, CLR_BLACK) ;
  9331.  
  9332.       GpiBeginPath (hps, ID_PATH) ;
  9333.       GpiCharStringAt (hps, &ptl, cbText, szText) ;     // Text string
  9334.       GpiEndPath (hps) ;
  9335.  
  9336.       GpiSetLineWidthGeom (hps, 6L) ;                   // 1/12 inch
  9337.       GpiModifyPath (hps, ID_PATH, MPATH_STROKE) ;
  9338.       GpiSetClipPath (hps, ID_PATH, SCP_AND | SCP_ALTERNATE) ;
  9339.  
  9340.       dMaxRadius = sqrt (pow (cxClient / 2.0, 2.0) +
  9341.                          pow (cyClient / 2.0, 2.0)) ;
  9342.                                                         // Draw spokes
  9343.       for (i = 0 ; i < 360 ; i++)
  9344.            {
  9345.            GpiSetColor (hps, lColors[i % iNumColors]) ;
  9346.  
  9347.            ptl.x = cxClient / 2 ;
  9348.            ptl.y = cyClient / 2 ;
  9349.            GpiMove (hps, &ptl) ;
  9350.  
  9351.            ptl.x += (LONG) (dMaxRadius * cos (i * 6.28 / 360)) ;
  9352.            ptl.y += (LONG) (dMaxRadius * sin (i * 6.28 / 360)) ;
  9353.            GpiLine (hps, &ptl) ;
  9354.            }
  9355.       GpiSetCharSet (hps, LCID_DEFAULT) ;               // Clean up
  9356.       GpiDeleteSetId (hps, LCID_MYFONT) ;
  9357.       }
  9358.  
  9359.  Is It Enough?
  9360.  
  9361.  I think it's clear that the facilities provided by GPI for working with
  9362.  vector fonts equal--and sometimes exceed--those in PostScript. The
  9363.  GPI interface is very powerful and very versatile.
  9364.  
  9365.  Is that enough? No, it's not. The implementation of vector fonts in GPI has
  9366.  a structural flaw that still leaves PostScript the king.
  9367.  
  9368.  Take a close and careful look at Figure 3 and the display of the Helv font.
  9369.  You'll notice that the two legs of the H are different in width by one pixel
  9370.  when they should be the same width. This is undoubtedly caused by a rounding
  9371.  error. It's obviously more noticeable on a low-resolution video display than
  9372.  it would be on a 300 dpi laser printer, but even on a laser printer such
  9373.  errors will affect the legibility of the text.
  9374.  
  9375.  Errors such as this do not occur with PostScript fonts. PostScript fonts are
  9376.  true algorithms that are able to recognize and correct any anomalies in the
  9377.  rendition of the characters. In contrast, GPI fonts (which are encoded as a
  9378.  simple series of polylines and polyfillets) are drawn blindly without any
  9379.  feedback or correction.
  9380.  
  9381.  So, while we can rejoice in what we have in GPI, there is still the need for
  9382.  some improvement.
  9383.  
  9384.  Basic as a Professional Programming Language (An Interview with Ethan Winer)
  9385.  
  9386.  C and Macro Assembler (MASM) are the languages of choice for most
  9387.  programmers working in the personal computing environment. Occasionally one
  9388.  hears of a programmer using BASIC, but generally what one hears from
  9389.  professional programmers is that BASIC is a toy language--nice for
  9390.  learning, but hardly a language for serious program development.
  9391.  
  9392.  That attitude is beginning to change. In fact, BASIC has grown up quite a
  9393.  bit over the past year. The Microsoft(R) QuickBASIC Version 4 user interface
  9394.  provides an effective working environment for both the beginner and the
  9395.  advanced user, and the BASIC language itself, via the Microsoft BASIC
  9396.  Compiler Version 6.0, now has the professional language features programmers
  9397.  need.
  9398.  
  9399.  One believer in the ability of BASIC to handle the needs of programmers is
  9400.  Ethan Winer, founder of Crescent Software--a firm that specializes in
  9401.  software development in BASIC--as well as the developer of QuickPak
  9402.  Professional, a library of advanced routines designed especially for the
  9403.  BASIC compiler environment. Winer has also written numerous articles on
  9404.  BASIC for many industry publications.
  9405.  
  9406.  Recently we talked to Winer, who shared with us the reasons BASIC is his
  9407.  language of choice, his reasons for developing QuickPak Professional, and
  9408.  some insights into the technical issues involved in BASIC programming.
  9409.  
  9410.  MSJ: Why has BASIC become the language of choice for you and Crescent
  9411.  Software?
  9412.  
  9413.  EW: Unlike C and Pascal, Microsoft BASIC has always included a wealth of
  9414.  built-in commands and features. It can produce sophisticated graphics on an
  9415.  assortment of monitors, open and manipulate files and devices, play music,
  9416.  and access memory and hardware ports directly. The release of Microsoft
  9417.  QuickBASIC 4 and the BASIC 6.0 compiler, has added even more commands and
  9418.  features.
  9419.  
  9420.  BASIC has always been the easiest of the high-level languages to use, and
  9421.  the latest version is as powerful and capable as either C or Pascal.
  9422.  Furthermore, Microsoft has made clear they intend to keep improving both
  9423.  their BASIC 6.0 Professional Compiler and Microsoft QuickBASIC.
  9424.  
  9425.  Of the features that raise BASIC to the level of other professional
  9426.  languages, which do you find particularly useful?
  9427.  
  9428.  The overwhelming advantage of BASIC is that it's extremely easy to learn
  9429.  and use; the commands and functions have sensible names and their purpose is
  9430.  usually obvious. Unlike C or assembly language, it is almost impossible to
  9431.  overwrite the operating system or corrupt memory unless you really try, but
  9432.  this doesn't limit the control a program has over the system resources. For
  9433.  example, the PEEK and POKE commands let you read or write to any memory
  9434.  address, and INP and OUT will directly access hardware ports.
  9435.  
  9436.  Another important advantage of BASIC is that it handles type conversion
  9437.  automatically. That is, you can freely multiply an integer times a double
  9438.  precision variable, and BASIC will automatically handle the math. String
  9439.  handling is equally powerful; there are functions to assign or extract
  9440.  characters anywhere in a string. Graphics is yet another powerful feature.
  9441.  BASIC has built-in commands to draw circles, boxes, filled boxes, and so
  9442.  forth, on all of the popular monitor types.
  9443.  
  9444.  Why did Crescent Software focus on developing a library of programmer's
  9445.  tools for BASIC?
  9446.  
  9447.  There is no disputing the fact that programming languages have come a long
  9448.  way in the past few years. Yet no matter how complete or well designed a
  9449.  language is, programmers will always find they need some additional
  9450.  capability or feature. Third party libraries--or toolboxes--have
  9451.  traditionally filled that gap. A language like C requires external library
  9452.  support, due to its inherent limitations. For example, "pure" C can neither
  9453.  clear the screen nor locate the cursor, so C programmers must use toolbox
  9454.  routines, often viewing them as a necessary fact of life. Likewise, Pascal
  9455.  has its own limitations, and toolboxes are now equally common for that
  9456.  language as well.
  9457.  
  9458.  But perhaps the most important reason to use a third-party library is to
  9459.  reduce the effort needed to achieve a completed program. No doubt
  9460.  knowledgeable programmers could design a pull-down menu system or a
  9461.  full-featured text editor with mouse support, but that requires an enormous
  9462.  amount of time, time better spent designing the rest of the program.
  9463.  Furthermore, end users are used to snappy screen displays, pop-up windows,
  9464.  and all the other hallmarks of a sophisticated user interface. A good
  9465.  toolbox product can provide "canned" solutions to such programming problems,
  9466.  as well as enhance and extend a language.
  9467.  
  9468.  Did the library simply emerge from a collection of utilities you built up
  9469.  over time, or did you set out specifically to create the library?
  9470.  
  9471.  Designing a set of tools requires much more effort than, say, merely writing
  9472.  an interface to the various DOS and BIOS services. We would often start
  9473.  designing a program only to discover that one or more special support
  9474.  routines were also needed. In many cases our needs led to the development of
  9475.  a custom function or subroutine that turned out to be useful in a broader
  9476.  sense.
  9477.  
  9478.  One example is a set of functions that returns the minimum and maximum of
  9479.  two values. These are used extensively in many of the BASIC programs
  9480.  provided in QuickPak Professional and they eliminate what would otherwise be
  9481.  many IF/THEN statements. The MinInt assembler function returns the minimum
  9482.  of two integer values, eliminating such code as:
  9483.  
  9484.  IF X > MaxAllowed THEN
  9485.      Value = MaxAllowed
  9486.  ELSE
  9487.      Value = X
  9488.  END IF
  9489.  
  9490.  Instead a single statement does the same thing, but much more elegantly:
  9491.  
  9492.  Value = MinInt(X, MaxAllowed)
  9493.  
  9494.  We made many other custom functions to assist the QEdit editor that also
  9495.  resulted in meaningful additions to the package.
  9496.  
  9497.  In designing QuickPak Professional we had several very clear goals in mind.
  9498.  We wanted to provide ready-made solutions to common programming problems.
  9499.  These fall into two general categories: routines that are difficult for most
  9500.  programmers to create themselves, and services BASIC just cannot do
  9501.  directly.
  9502.  
  9503.  An example of the first category would be routines written in assembly
  9504.  language, perhaps to search or sort an array very quickly. Other difficult
  9505.  programs would be a text editor, a spreadsheet, and a delay timer with
  9506.  microsecond resolution.
  9507.  
  9508.  The second category is comprised of DOS and BIOS interrupts, because BASIC
  9509.  cannot easily access them. You would need these to list the files on a disk,
  9510.  change their date and time, or read and write a disk's volume label.
  9511.  
  9512.  Second, we wanted these services to be very easy to use. For example, all of
  9513.  the routines that search and process strings would be in both case sensitive
  9514.  and insensitive versions. It was also important to limit the number of
  9515.  passed parameters to the absolute minimum, and implement the routines as
  9516.  functions where appropriate. Using functions rather than called subroutines
  9517.  is the most natural way to extend BASIC.
  9518.  
  9519.  Third, in some cases BASIC's error handling abilities are not powerful
  9520.  enough. C or Pascal let you try opening a file and then check whether an
  9521.  error occurred, but BASIC requires a special error handling subroutine set
  9522.  up in advance. If an error occurs, you end up in the error
  9523.  handler--often with no idea of how you got there or where in the
  9524.  program to return to. So some means to test a disk drive or printer or even
  9525.  bypass BASIC's file handling altogether would be a useful addition.
  9526.  
  9527.  From the looks of all the source code and tutorials you've provided, it
  9528.  would appear that you have very strong convictions about teaching BASIC.
  9529.  
  9530.  Yes. What programmers often need is not just more language features, but an
  9531.  understanding of how to use those capabilities already present. The single
  9532.  most useful tool a programmer can acquire is knowledge.
  9533.  
  9534.  Most accomplished programmers will tell you the best way to become
  9535.  proficient is by studying other people's programs. Indeed, many programmers
  9536.  are self-taught, learning solely from books and articles in popular computer
  9537.  magazines. By using someone else's program, studying the source code and
  9538.  perhaps even modifying it, a much deeper understanding results.
  9539.  
  9540.  We want people to understand how these routines work, and be able to learn
  9541.  from them. This means not only providing all of the BASIC and assembly
  9542.  source, but also writing a series of tutorials explaining the underlying
  9543.  concepts.
  9544.  
  9545.  The manuals that come with Microsoft QuickBASIC 4 are excellent as far as
  9546.  they go, but they gloss over a number of important topics; for example
  9547.  passing TYPE variables and arrays to subprograms and functions. Instead, the
  9548.  examples that use a TYPE array skirt the issue by declaring the array as
  9549.  SHARED. Likewise, the manual makes no mention of saving and loading arrays
  9550.  and EGA screen images to disk.
  9551.  
  9552.  Programmers who would like to become more proficient in BASIC must
  9553.  understand these concepts. QuickPak Professional includes much of this
  9554.  information, along with tutorials on accessing files, storing string and
  9555.  numeric data in a program's code segment, and a comparison of the various
  9556.  ways procedures are designed.
  9557.  
  9558.  One of the nice things about BASIC is that it relieves the programmer of the
  9559.  need to know about the nuts and bolts of the operating system. Unfortunately
  9560.  this also results in some limitations. Can you provide an example where
  9561.  you've removed some of these limitations while keeping BASIC's ease of use?
  9562.  
  9563.  BASIC has many built-in commands, but it is admittedly lacking in the
  9564.  ability to access DOS and BIOS interrupts. Microsoft QuickBASIC 2 added a
  9565.  form of the CALL INTERRUPT feature, though only as an add-on available by
  9566.  linking with a special object module. Further, CALL INTERRUPT is clumsy to
  9567.  implement, and its use requires a knowledge of DOS services that many BASIC
  9568.  programmers do not possess.
  9569.  
  9570.  Rather than simply provide a "watered down" replacement for CALL INTERRUPT,
  9571.  we felt it was important that the BASIC programmer not have to understand
  9572.  how DOS and BIOS services are accessed.
  9573.  
  9574.  Let me give you an example. Any DOS function that accepts a file name
  9575.  expects that name to be in an ASCIIZ format; this is how C strings are
  9576.  stored. Unlike C, BASIC strings do not contain a zero byte to mark the end;
  9577.  instead, a string descriptor is maintained for each string or string array
  9578.  element. A string descriptor is a 4-byte table containing the length of the
  9579.  string and the address of the actual data.
  9580.  
  9581.  Using CALL INTERRUPT required BASIC programmers to add the zero byte
  9582.  manually whenever a file name has to be passed to a DOS file service. Since
  9583.  one of our highest priorities was ease of use, we instead chose to have the
  9584.  file name copied to a temporary holding area; then the routine adds the
  9585.  terminating zero byte before calling DOS.
  9586.  
  9587.  An important DOS service most programs need is the ability to get a list of
  9588.  file names from disk. No application worth its salt will force the operator
  9589.  to remember the names of files to be loaded. Instead, a menu should list all
  9590.  the files present, with some sort of "point and shoot" method provided for
  9591.  selecting one.
  9592.  
  9593.  DOS provides no direct way to get a complete list of file names in one
  9594.  operation. Even if it did, the BASIC program would need to know beforehand
  9595.  the number of names present so sufficient string memory could be reserved
  9596.  for them. The solution we devised was to create a function that returns the
  9597.  number of file names matching a given search specification. Once this is
  9598.  known, a BASIC string array may be reserved for them, and a second
  9599.  subroutine retrieves all of the names from DOS at once.
  9600.  
  9601.  This brings up an interesting point. Microsoft BASIC provides two ways to
  9602.  create a subroutine: subprograms accessed with the CALL keyword, and
  9603.  functions invoked from a BASIC expression. You seem to rely heavily on
  9604.  functions.
  9605.  
  9606.  Functions are indeed useful and a major and welcome addition. One can
  9607.  develop a BASIC function in either BASIC or assembly language. The
  9608.  instructions for implementing an assembler function in QuickBASIC can be
  9609.  found in the Mixed-Language Programming Guide that comes with Microsoft
  9610.  Macro Assembler Version 5 (MASM).
  9611.  
  9612.  One major advantage of a function is the elimination of a passed parameter,
  9613.  in situations where it is appropriate. If the routine that returns the
  9614.  number of matching files were set up as a program it would be called
  9615.  like this:
  9616.  
  9617.  CALL FCount("*.*", Count)
  9618.  PRINT Count; "files were found"
  9619.  
  9620.  But designing the same routine as a function makes it both easier to use and
  9621.  understand:
  9622.  
  9623.  PRINT FCount("*.*");_
  9624.   "files were found"
  9625.  
  9626.  The output of a BASIC function may be used directly within a PRINT statement
  9627.  or, in the case of setting aside sufficient string elements, as part of the
  9628.  DIM command:
  9629.  
  9630.  DIM Array$(FCount("*.*"))
  9631.  
  9632.  Using a function like this is the most natural way to extend the BASIC
  9633.  language, and since one less parameter is needed, a function will be faster
  9634.  than an equivalent called subroutine.
  9635.  
  9636.   Parameters in all Microsoft languages are passed on the stack, and it is no
  9637.  secret that stack operations can be very slow. This is why functions are
  9638.  especially good when used without parameters. For instance, we have included
  9639.  a set of functions to return the status of the various keyboard settings.
  9640.  The usual method to determine the status of, say, the Alt key is to use
  9641.  BASIC's DEF SEG, PEEK, and AND commands:
  9642.  
  9643.  DEF SEG = 0
  9644.  AltKey = PEEK(&H417) AND 8
  9645.  IF AltKey THEN PRINT_
  9646.      "the Alt key is depressed"
  9647.  
  9648.  By using a dedicated function, you can replace all that code with a single
  9649.  statement like:
  9650.  
  9651.  IF AltKey THEN PRINT_
  9652.      "the Alt key is depressed"
  9653.  
  9654.  Better still, without passed parameters only five instructions in assembler
  9655.  code are necessary to implement such a function.
  9656.  
  9657.  Assembly language surfaces again. It would appear that a knowledge of MASM
  9658.  might be necessary even for the BASIC programmer. What do you think?
  9659.  
  9660.  Many programmers who use high-level languages would like to learn assembly
  9661.  language but are intimidated by what they believe will be a long and painful
  9662.  process. Learning assembly language is actually not that painful, and even a
  9663.  casual understanding from the perspective of a BASIC programmer is useful.
  9664.  Of course, you need not understand assembly language to use an assembler
  9665.  routine.
  9666.  
  9667.  Are there any special things to know about assembly language functions?
  9668.  
  9669.  The most obvious thing that comes to mind is that, before an assembly
  9670.  language function can be used in Microsoft BASIC, the program that uses the
  9671.  function must declare it. Having to declare procedures is new to BASIC, but
  9672.  in this case it is not unreasonable. Or else--in the Alt key
  9673.  example--Microsoft QuickBASIC, would assume that the Alt key is simply
  9674.  an integer variable. By declaring it ahead of time, Microsoft QuickBASIC
  9675.  knows that the Alt key is a function to be called, and the value returned in
  9676.  AX holds the result.
  9677.  
  9678.  And using assembly language speeds things up here and there.
  9679.  
  9680.  That's an equally important reason for using assembly language. We wanted to
  9681.  speed up those operations that typically are very slow in any high-level
  9682.  language. Again, assembly language is the key to great performance, and
  9683.  about two-thirds of the routines in QuickPak Professional are in assembler.
  9684.  
  9685.  The majority of MS-DOS(R) language compilers, for example, do screen
  9686.  printing through the operating system in order to be compatible with as many
  9687.  machines as possible. Whether this is done via DOS or the BIOS, the results
  9688.  are too slow. Microsoft QuickBASIC 4 partly addresses this by writing
  9689.  directly to video memory, but there is still much room for improvement. Each
  9690.  character must be examined to see if it is a special control character and
  9691.  for each character it takes extra time just to see if the screen needs to be
  9692.  scrolled. We devised several quick printing routines to accommodate multiple
  9693.  video pages and to display new text without destroying the colors already on
  9694.  the screen.
  9695.  
  9696.  One particularly useful routine displays a portion of a string array,
  9697.  containing it within a specified area of the screen. This greatly simplifies
  9698.  the creation of, for example, a browse facility, where you can scroll up and
  9699.  down through the entire array on the screen. Again, our intent was to have
  9700.  these routines do as much as possible and save the programmer unnecessary
  9701.  work.
  9702.  
  9703.  Assembler routines would also give the BASIC programmer substantially faster
  9704.  array processing.
  9705.  
  9706.  This is exactly where assembly language really gives BASIC a much needed
  9707.  boost, in the area of processing arrays. We have, for example, provided a
  9708.  set of functions for each of Microsoft BASIC's numeric array types to return
  9709.  the largest or smallest values. Using a dedicated assembler routine is
  9710.  typically six or seven times faster than an equivalent function in BASIC.
  9711.  
  9712.  An even greater improvement can be had when saving an entire string array to
  9713.  disk and then reloading it later. One of the slowest operations BASIC
  9714.  performs is reading data from a sequential file; it must examine every
  9715.  single byte to see if it is either a carriage return that marks the end of a
  9716.  string, or the CHR$(26) byte that marks the end of a file. Creating a custom
  9717.  assembler routine to capture the entire file in one operation saves an
  9718.  enormous amount of time.
  9719.  
  9720.  The fastest way to save a numeric array in BASIC is to use BSAVE, as opposed
  9721.  to making multiple PRINT statements to a file. The major problem with the
  9722.  latter is the overhead required to convert the values from the internal
  9723.  format used by the PC into the equivalent ASCII digits. Further, BSAVE takes
  9724.  up less disk space.
  9725.  
  9726.  In the case of integers, only 2 bytes are used to store the number in
  9727.  memory, regardless of its value. Contrast that with up to five digits (six
  9728.  if the number is negative) to store the number as ASCII digits. Worse, each
  9729.  number in the file would also need either a carriage return/line feed pair,
  9730.  or a delimiting comma. BSAVE instead captures a "snapshot" of any area of
  9731.  memory, and sends it to disk in one operation. The complementary command is
  9732.  BLOAD, which retrieves a previously BSAVE'd file.
  9733.  
  9734.  What about string arrays?
  9735.  
  9736.  Unlike numeric arrays, BASIC string arrays do not occupy contiguous memory
  9737.  addresses. Instead, the descriptor tables are contiguous, and the actual
  9738.  data could be anywhere in near memory. Therefore, before BSAVE can be used
  9739.  on a string array, the data from all of the elements must be gathered up
  9740.  into a single block. A pair of dedicated routines processes all of the
  9741.  string elements in one operation, and copies them to an integer array and
  9742.  back again. An additional routine lets the programmer retrieve an individual
  9743.  string element if needed.
  9744.  
  9745.  Numeric arrays in Microsoft QuickBASIC are not restricted to near memory.
  9746.  Just being able to copy a string array out to far memory is a useful
  9747.  feature. And if far memory becomes congested, it's easy to save the arrays
  9748.  to disk to open additional space. Further, because we delimit the end of
  9749.  each string with a carriage return/line feed pair, the file is directly
  9750.  readable by any application.
  9751.  
  9752.  You've provided BASIC users with two "new" data types. What are Very Long
  9753.  Integers and Bit Arrays?
  9754.  
  9755.  While I was working on QuickPak Professional, several interesting concepts
  9756.  and routines resulted, including the development of two "new" variable
  9757.  types.
  9758.  
  9759.  One important and long-overdue feature introduced with Microsoft QuickBASIC
  9760.  4 is support for long (4-byte) integers. Where appropriate, long integers
  9761.  have a decided advantage over floating point numbers since you can process
  9762.  them very quickly, without any rounding errors. Accountants like that.
  9763.  Considered as pennies, their range of +/-$21,474,836.47 is generally
  9764.  adequate--except for serious financial work.
  9765.  
  9766.  As a solution, we devised a set of routines for adding, subtracting,
  9767.  multiplying, and dividing what we call Very Long Integers. These variables
  9768.  use 8 bytes apiece and let you manipulate extremely large numbers without
  9769.  rounding errors. You assign a Very Long by aliasing it into a conventional
  9770.  double precision variable, and BASIC isn't any the wiser.
  9771.  
  9772.  In the opposite direction, we also created a pair of routines to manipulate
  9773.  Bit Arrays. The smallest variable that BASIC provides is a 16-bit integer.
  9774.  When a program needs the range of values offered by integers, using them
  9775.  makes a lot of sense. But often all that is needed are simple true or false
  9776.  variables, and an integer array can waste an enormous amount of memory. A
  9777.  1000-element bit array occupies only 125 bytes, compared to 2000 bytes for a
  9778.  conventional integer array.
  9779.  
  9780.  There is a routine you supply that lets a BASIC program write to two
  9781.  monitors at the same time. How did you accomplish that?
  9782.  
  9783.  It isn't difficult to create a routine that changes the active
  9784.  monitor, and the very act of switching monitors always clears the screen in
  9785.  the process. Because there is nothing to prevent a program from writing
  9786.  directly to screen memory for an inactive monitor, our solution was both
  9787.  effective and easy to implement.
  9788.  
  9789.  All of the video routines in QuickPak Professional automatically determine
  9790.  the type of monitor installed when called. This is necessary for two
  9791.  reasons. First, the video segment is different for color and monochrome
  9792.  monitors, so the correct segment must be known before a routine can write
  9793.  directly to screen memory. Second, and equally important, CGA monitors
  9794.  create "snow" unless the reading and writing is synchronized to the
  9795.  horizontal retrace.
  9796.  
  9797.  In the routine that writes to any monitor, though, the caller must instead
  9798.  indicate the type of monitor to write to with a code; 1 means monochrome, 2
  9799.  is a CGA, and 3 means an EGA or VGA. Even though a PC can support two
  9800.  monitors at once, they must be different--that is, both cannot be
  9801.  color, or both monochrome. Thus the programmer would simply specify the
  9802.  monitor type that is not the current one. Of course there is also a function
  9803.  that reports the currently active type of monitor.
  9804.  
  9805.  You've also given BASIC programmers a way to dump screen images regardless
  9806.  of the screen mode they may be in. What can you tell us about that?
  9807.  
  9808.  We also wanted to address graphics. A major limitation in the
  9809.  GRAPHICS.EXE program that comes with DOS is that it works only in the CGA
  9810.  screen modes. In addition, GRAPHICS works only with printers that are
  9811.  compatible with the Epson(R)/IBM(R) command set. Since BASIC supports the
  9812.  EGA, VGA, and Hercules(R) standards, it was important to provide a routine
  9813.  that could print a graphics image from any of these modes.
  9814.  
  9815.  We realized that to be truly useful a screen print routine must know not
  9816.  only the Epson printer codes (which all modern printers can emulate), but
  9817.  also the HP(R) LaserJet(R) codes. Since a LaserJet can print in several
  9818.  sizes, the caller should be able to specify which resolution to use. The
  9819.  only remaining problem was what to do with the colors.
  9820.  
  9821.  A stunning three-dimensional pie chart displayed in sixteen colors is
  9822.  useless as a printout; all you'd get is a big black circle. Clearly, the
  9823.  best solution was to substitute a different hatching pattern for each of the
  9824.  possible screen colors. But what complicates things considerably is the
  9825.  different ways that graphics screen memory is organized.
  9826.  
  9827.  In text modes, each successive character on the screen is contained in
  9828.  successive bytes in display memory. But in CGA graphics modes the screen
  9829.  memory is organized using a method known as interlacing. In an interlaced
  9830.  system, every other row is in a different block of memory, which makes
  9831.  accessing the memory much more difficult.
  9832.  
  9833.  Hercules graphics also interlaces, except that it uses every fourth row. EGA
  9834.  and VGA adapters use yet another system, where each color is in an entirely
  9835.  different segment. This complicated both reading screen memory and
  9836.  translating the colors into hatching patterns.
  9837.  
  9838.  One of your accomplishments was the creation of a word processor, QEdit,
  9839.  written in BASIC. BASIC's variable length strings must have been very useful
  9840.  there.
  9841.  
  9842.  Languages like C or Pascal that permit only fixed length strings take much
  9843.  more programming to get the same results. The program either wastes a
  9844.  substantial amount of memory for text lines that are shorter than the
  9845.  maximum, or it must maintain a table of pointers to keep track of where in
  9846.  memory each string begins. As characters and lines are inserted or deleted,
  9847.  the pointers must be constantly updated.
  9848.  
  9849.  The fact that BASIC supports variable length strings reduced our effort
  9850.  considerably. BASIC does all of this very quickly and automatically, and
  9851.  wordwrapping in QEdit will keep up with even the fastest typist.
  9852.  
  9853.  [See code samples at end of article for an example of a wordwrap function in
  9854.  BASIC, and an example of how to code a function to obtain screen colors]
  9855.  
  9856.  Variable length strings need to be dynamically allocated. This must have
  9857.  caused some significant problems.
  9858.  
  9859.  One of our biggest difficulties was creating the string array processing
  9860.  routines, which required additional research. In particular, we had to get a
  9861.  fair amount of information about Microsoft BASIC's internal workings. As you
  9862.  mentioned, since BASIC permits strings of nearly any length, they are
  9863.  allocated dynamically as necessary. This complicates memory management
  9864.  considerably and, not surprisingly, Microsoft considers many of those
  9865.  details to be proprietary.
  9866.  
  9867.  This became evident immediately when we started writing the routines to sort
  9868.  a string array. I noted earlier that BASIC strings use a descriptor table to
  9869.  tell each string's length and memory location. At first glance, it seems
  9870.  that any two strings can be exchanged by just swapping their descriptor
  9871.  tables. Descriptors are well documented in the Microsoft QuickBASIC manuals,
  9872.  but unfortunately that isn't the entire story.
  9873.  
  9874.  What is not mentioned is how string data is tied. It took us several days to
  9875.  figure it out by trial and error.
  9876.  
  9877.  Once we could exchange strings for sorting purposes, it was a simple matter
  9878.  to insert or delete elements in an array. On a standard IBM PC, inserting a
  9879.  single string at the beginning of a 2000 element array takes more than two
  9880.  seconds using Microsoft QuickBASIC. Contrast that to less than a tenth of a
  9881.  second for the equivalent routine written in assembler.
  9882.  
  9883.  Some of the string functions in QuickPak must have caused similar problems.
  9884.  But again it seems that by providing "functions" you've made life easier.
  9885.  
  9886.   String functions were indeed yet another difficult feature to
  9887.  implement--and again, because of the lack of information. Besides the
  9888.  advantage of eliminating a passed parameter, a function that can directly
  9889.  return a string saves the programmer from having to set aside space
  9890.  beforehand.
  9891.  
  9892.  One of the routines in QuickPak Professional obtains the current directory
  9893.  for a specified drive. Since a directory name can be as long as 64
  9894.  characters, such a routine would first need a string of that length
  9895.  allocated. When the routine is finished, the extra characters at the end
  9896.  must be removed manually.
  9897.  
  9898.  Because DOS uses a zero byte to mark the end of any strings it returns, the
  9899.  program must use BASIC's INSTR function to find that byte; then it keeps
  9900.  only those characters that precede it.
  9901.  
  9902.  Dir$ = SPACE$(64)
  9903.      'set aside 64 bytes
  9904.  Drive$ = "C"
  9905.      'specify which drive
  9906.  CALL GetDir(Drive$, Dir$)
  9907.      'GetDir loads Dir$
  9908.  Zero = INSTR(Dir$, 0)
  9909.      'find the zero byte
  9910.  Dir$ = LEFT$(Dir$, Zero--1)
  9911.      'keep what's before the 0
  9912.  
  9913.  When GetDir is designed as a string function, it is considerably easier to
  9914.  use:
  9915.  
  9916.  Drive$ = "C"
  9917.  Dir$ = GetDir$(Drive$)
  9918.  
  9919.  or
  9920.  
  9921.  Dir$ = GetDir$("a")
  9922.  
  9923.  As a function, GetDir locates the zero byte, and then returns a string that
  9924.  is only as long as actually needed.
  9925.  
  9926.  Let's go back and talk about QEdit again. You seem to have emulated the
  9927.  Microsoft QuickBASIC 4 edit commands in QEdit. With all of the editors
  9928.  already available, why did you create another?
  9929.  
  9930.  Though there certainly are many editor programs available commercially, I
  9931.  don't know of any that come with free source code, and none meant to be
  9932.  added to another program. People often ask us to develop new routines, but
  9933.  by far the one they request most is a text editor that can be customized and
  9934.  added to their own programs. Of course, "editor" means different things to
  9935.  different people, and every programmer has his or her own idea of how one
  9936.  should work.
  9937.  
  9938.  Because our customers are familiar with the Microsoft QuickBASIC 4 edit
  9939.  commands, it made the most sense to emulate those. But since we provide the
  9940.  editor's source code, it is easy to customize the various keystrokes that
  9941.  are recognized. But deciding on the editing commands was just the beginning.
  9942.  
  9943.  To be truly useful, a text editor must be able to accept text both as
  9944.  individual lines and in the "one line per paragraph" method of many word
  9945.  processors. We also wanted the operator to be able to change margins, size
  9946.  the window, and scroll the text using a mouse--without the calling
  9947.  program having to do any additional work. Of course, keystrokes must be
  9948.  processed quickly enough to be totally transparent to the user.
  9949.  
  9950.  QEdit contains a number of important features such as full mouse support,
  9951.  block operations, and on-line help. But perhaps the most important feature
  9952.  is the ability to remove features that aren't needed. After all, if someone
  9953.  wants a bare bones editor with margins and wordwrap, there should be a way
  9954.  to exclude the code for block operations and mouse support. We therefore
  9955.  placed those portions of QEdit into separate sections, where users can
  9956.  easily delete or remark them out.
  9957.  
  9958.  One additional feature we wanted for our own use was block operations on
  9959.  columns, as well as for words and lines. Most editors work only in sentence
  9960.  mode, so that marking a block downward captures the entire length of the
  9961.  line. In a word processor this is often sufficient. But when programming,
  9962.  the ability to mark a long section and instantly change the indent level is
  9963.  very useful. A column mode also simplifies copying or moving a table of
  9964.  numbers.
  9965.  
  9966.  Apparently QEdit was designed for use as a pop-up utility.
  9967.  
  9968.  In reality, though QEdit is not meant as a standalone word processor, it
  9969.  certainly could be. It was, however, designed so that users can add it as a
  9970.  "pop-up" to a main BASIC program, thus showing that even sophisticated
  9971.  programs can be written in BASIC.
  9972.  
  9973.  What sort of problems did you encounter?
  9974.  
  9975.  Because of the number of features in QEdit, it uses four separate help
  9976.  screens, accessed with the F1 key. The help text is in the code segment of
  9977.  the program, and a special assembler routine retrieves it. This saves nearly
  9978.  2Kb of string memory. Also, since users can remove the mouse and block
  9979.  operations, a variable is modified in each of those sections so the help
  9980.  code will know which screens are appropriate.
  9981.  
  9982.  Besides storing the help text outside of string memory, we used the same
  9983.  technique for the cut-and-paste clipboard. Otherwise, with a fairly large
  9984.  document loaded, BASIC could run out of memory when capturing the block.
  9985.  Which brings up an important point. As difficult as it was to capture a
  9986.  column that may span several screens, deleting or pasting it somewhere else
  9987.  was even tougher. Moreover, to prevent the text block from stealing string
  9988.  space, the clipboard is kept in an integer array. We had already written a
  9989.  string manager to copy all or part of a string array to an integer array and
  9990.  back, but capturing and pasting a column required two additional custom
  9991.  assembler routines.
  9992.  
  9993.  One routine copies elements from a string array to an integer array, but
  9994.  starting at a given offset and for a specified number of characters. If a
  9995.  line ends short of the column width, it pads the remainder with blanks. The
  9996.  second custom routine retrieves the string portions one by one from the
  9997.  integer array, so the wordwrap routine can insert the clipboard contents
  9998.  back into the text.
  9999.  
  10000.  We also needed a routine to close a portion of a screen that had been saved
  10001.  earlier. Because QEdit is a pop-up, it saves the underlying screen
  10002.  automatically. When the screen size changes, it complicates matters.
  10003.  
  10004.  So you also had to develop routines for sizing windows on the screen. How
  10005.  was this handled?
  10006.  
  10007.  The user can at any time place the mouse cursor at the upper left or bottom
  10008.  right corner, and move the corner to a new location. Of course, sizeable
  10009.  windows are nothing new, but we wanted the window to actually change, rather
  10010.  than simply show where it would be upon release of the mouse button.
  10011.  
  10012.  With some programs, changing the window size produces an enormous amount of
  10013.  flicker. In QEdit, we employed a custom routine to close only that portion
  10014.  of the window actually needed to  reduce the window size.
  10015.  
  10016.  We developed many other related routines both for QEdit and QuickPak
  10017.  Professional in general. For example, one quickly scans a string array for
  10018.  the number of active lines. Another contains the mouse cursor within a
  10019.  specified area on the screen. Another determines the current video mode so a
  10020.  program can accommodate 25, 43, or 50 lines automatically.
  10021.  
  10022.  As you can see, an enormous amount of detail is required to implement a
  10023.  full-featured word processor, regardless of the language used. Microsoft
  10024.  QuickBASIC and the Microsoft BASIC 6.0 compiler are ideal for any
  10025.  application that requires extensive string manipulation, but they are
  10026.  especially appropriate for a word processor.
  10027.  
  10028.  You have certainly made a strong case for BASIC as a "real" language.
  10029.  
  10030.  I truly believe in BASIC as a serious applications language. It has made
  10031.  computer programming--real programming--accessible to millions of
  10032.  people. It is unfortunate that many otherwise informed programmers view
  10033.  BASIC 6.0 and Microsoft QuickBASIC as unsuitable for "real" applications
  10034.  simply because of the BASIC interpreter that comes with DOS. Besides
  10035.  exceptional performance, the Microsoft BASIC products also support
  10036.  recursion, structured code and data, and many other features needed for a
  10037.  modern, professional language.
  10038.  
  10039.  Sample Code:
  10040.  
  10041.  DEFINT A-Z
  10042.  DECLARE SUB WordWrap (X$, Wide)
  10043.  
  10044.  CLS
  10045.  
  10046.  A$ = "BASIC strings use a descriptor table ot tell each string's"
  10047.  B$ = "length and memory location. At first glance, it seems that"
  10048.  C$ = "any two strings can be exchanged by just swapping their"
  10049.  D$ = "descriptor tables. Once we could exchange strings for"
  10050.  E$ = "sorting purposes, it was a simple matter to insert or"
  10051.  F$ = "delete elements in an array. On a standard IBM PC, inserting"
  10052.  G$ = "a single string at the beginning of a 2000 element array"
  10053.  H$ = "takes more thatn two seconds using Microsoft QuickBASIC."
  10054.  
  10055.  W$ = A$ + B$ + C$ + D$ + E$ + F$ + G$ + H$
  10056.  PRINT W$
  10057.  PRINT
  10058.  Wide = 60               'the maximum width of the display
  10059.  WordWrap W$, Wide
  10060.  
  10061.  
  10062.  
  10063.  SUB WordWrap (X$, Wide%)
  10064.  
  10065.    Length% = LEN(X$)           'remember the length
  10066.    Pointer% = 1                'start at the beginning of the string
  10067.  
  10068.    'scan a block of sixty characters backwards, looking for a blank
  10069.    'stop at the first blank, or if we reached the end of the string
  10070.     DO
  10071.       FOR X% = Pointer% + Wide% TO Pointer% STEP -1
  10072.          IF MID$(X$, X%, 1) = " " OR X% = Length% + 1 THEN
  10073.           'LOCATE , LeftMargin   'optional to tab in the left edge
  10074.            PRINT MID$(X$, Pointer%, X% - Pointer%);
  10075.           'LPRINT [TAB(LeftMargin)]; MID$(X$, Pointer%, X% - Pointer%)
  10076.            Pointer% = X% + 1
  10077.            WHILE MID$(X$, Pointer%, 1) = " "
  10078.             Pointer% = Pointer% + 1 'swallow extra blanks to next word
  10079.            WEND
  10080.            IF POS(0) > 1 THEN PRINT  'if cursor didn't wrap next line
  10081.                EXIT FOR                  'done with this block
  10082.             END IF
  10083.         NEXT
  10084.      LOOP WHILE Pointer% < Length%       'loop until done
  10085.  
  10086.  END SUB
  10087.  
  10088.  Sample 2:
  10089.  
  10090.  
  10091.  DEFINT A-Z
  10092.  DECLARE SUB GetColor (FG, BG)           'gets BASIC's current colors
  10093.  DECLARE SUB SplitColor (XColor, FG, BG) '.ASM - splits a color into FG
  10094.                                          ' and BG
  10095.  DECLARE FUNCTION OneColor% (FG, BG)     '.ASM - combines FG/BG into one
  10096.                                          ' color
  10097.  
  10098.  
  10099.  CLS
  10100.  INPUT "Enter a foreground color value (0 to 31): ", FG
  10101.  INPUT "Enter a background color value (0 to 7) : ", BG
  10102.  COLOR FG, BG
  10103.  
  10104.  PRINT : PRINT "BASIC's current color settings are: ";
  10105.  GetColor FG, BG
  10106.  PRINT FG; "and"; BG
  10107.  
  10108.  PRINT "That combines to the single byte value of"; OneColor%(FG, BG)
  10109.  PRINT "Broken back out results in";
  10110.  SplitColor OneColor%(FG, BG), NewFG, NewBG
  10111.  PRINT NewFG; "and"; NewBG
  10112.  
  10113.  COLOR 7, 0      'restore defaults before ending
  10114.  
  10115.  'This function obtains BASIC's current colors by first saving the
  10116.  'character and color in the upper left corner of the screen.  Next,
  10117.  'a blank space is printed there, and SCREEN is used to see what color
  10118.  'was used.  Finally, the original screen contents are restored.
  10119.  '
  10120.  SUB GetColor (FG%, BG%) STATIC
  10121.    V% = CSRLIN                       'save the current cursor location
  10122.    H% = POS(0)
  10123.    SaveChar% = SCREEN(1, 1)          'save the current character
  10124.    SaveColor% = SCREEN(1, 1, 1)      'and its color
  10125.    SplitColor SaveColor%, SaveFG%, SaveBG%
  10126.  
  10127.    LOCATE 1, 1                       'print with BASIC's current color
  10128.    PRINT " "; CHR$(29);              'back up the cursor to 1,1
  10129.    CurColor% = SCREEN(1, 1, 1)       'read the current color
  10130.    COLOR SaveFG%, SaveBG%            'restore the original color at 1,1
  10131.    PRINT CHR$(SaveChar%);            'and the character
  10132.  
  10133.    LOCATE V%, H%                     'put the cursor back where it was
  10134.    SplitColor CurColor%, FG%, BG%    'split the color into separate
  10135.                                      'FG and BG
  10136.    COLOR FG%, BG%                'restore BASIC's current value for it
  10137.  END SUB
  10138.  
  10139.  
  10140.  
  10141.  Organizing Data in Your C Program with Structures, Unions, and Typedefs
  10142.  
  10143.   Greg Comeau
  10144.  
  10145.  Many of you have no doubt used structures, unions, and typedefs in your C
  10146.  programming careers. In general, structures are easy to use, and they help
  10147.  in the programming paradigms involved with the correct usage of data in
  10148.  programs. They also keep code readable and maintainable. Unions and typedefs
  10149.  can perform some of these duties as well, but their usage tends to become
  10150.  more complicated and cryptic.
  10151.  
  10152.  Since the mechanism of struct and union is pretty well established, I will
  10153.  not spend time describing their basic syntax (the mechanism by which the
  10154.  grammar of the C language dictates the way they may be written) or semantics
  10155.  (the way the compiler treats them in a meaningful fashion). Instead I will
  10156.  describe some of the idiosyncrasies that crop up with structures, unions,
  10157.  and typedefs, providing a resource to use to avoid coding bugs, recognize
  10158.  portability problems, and know more about the C language.
  10159.  
  10160.  Recent Additions
  10161.  
  10162.  The initial specification of C appeared publicly in the classic text The C
  10163.  Programming Language by Brian W. Kernighan and Dennis M. Ritchie (herein
  10164.  referred to as K&R). In that specification, structure assignments, structure
  10165.  arguments, and functions that returned structures were not allowed. Present
  10166.  day compilers, however, allow these activities to occur, and these features
  10167.  have been formalized by the American National Standards Institute's draft
  10168.  proposal for C (ANSI C) which, I understand, will have no more public review
  10169.  periods and will at last become a bona fide standard sometime around March
  10170.  of 1989.
  10171.  
  10172.  Another feature that has made its way into that draft is initialization of
  10173.  automatic structures. However, this is not available under all compilers,
  10174.  especially those that typically come with the UNIX(R) or XENIX(R) Compiler
  10175.  Development Set. For example, the structure definition in Figure 1a will not
  10176.  compile under those compilers unless the static storage class is added as an
  10177.  attribute as is shown in Figure 1b.
  10178.  
  10179.  This places an extra burden  on the programmer and is due to the compiler
  10180.  following the older language specification rather than some technical
  10181.  constraint in compiler technology. This restriction may also force you to
  10182.  fill up more of your programs' static storage space area than you'd like. If
  10183.  space is at a premium, this may present problems. The alternative is to code
  10184.  explicit assignments for each member somewhere in the function. In terms of
  10185.  efficiency, this probably isn't going to make much of a difference since
  10186.  this is what the compiler would do with the initialization of the automatic.
  10187.  However, it would make your code more cluttered.
  10188.  
  10189.  Automatic unions and arrays used to have this problem too. They have been
  10190.  allowed to be initialized without constraints by the ANSI C proposal. Since
  10191.  this has become common practice and has been mandated by ANSI, I would think
  10192.  twice about any vendor that does not yet support this.
  10193.  
  10194.  Figure 1:
  10195.  
  10196.  A
  10197.  1    main()
  10198.  2    {
  10199.  3    struct {
  10200.  4        int a;
  10201.  5    } s = { 1 };
  10202.  6    }
  10203.  
  10204.  B
  10205.  1    main()
  10206.  2    {
  10207.  3    static struct {
  10208.  4        int a;
  10209.  5    } s = { 1 };
  10210.  6    }
  10211.  
  10212.  C
  10213.  1    main()
  10214.  2    {
  10215.  3    struct {
  10216.  4        int a;
  10217.  5    } s1, s2;
  10218.  6
  10219.  7    if (s1 == s2)    /* syntax error */
  10220.  8        printf("s1 == s2\n");
  10221.  9    else
  10222.  10        printf("s1 != s2\n");
  10223.  11    }
  10224.  
  10225.  Comparing Structures
  10226.  
  10227.  If you were to try to compile the example code from Figure 1c, you'd find
  10228.  that it produces a syntax error. This is because you cannot compare
  10229.  structures or unions by name the way you'd perform a structure assignment
  10230.  (for example, struct1 = struct2;). The only way to accomplish the comparison
  10231.  is by checking each member of the structure individually.
  10232.  
  10233.  This may sound like a silly restriction to place on a structure (like the
  10234.  inability to easily assign and initialize used to be) and it is--to an
  10235.  extent. However, there is a twofold reason for this. First, if the operation
  10236.  were allowed, it would be expected that it should work for the inequivalency
  10237.  operator (!=), for the relational operators (<, >, <=, >=), and perhaps even
  10238.  for some others. This would not be 100 percent desirable since it would tend
  10239.  to put the compiler through quite a few gyrations.
  10240.  
  10241.  You may very well think "So what?" about this point in time. But I haven't
  10242.  told you the entire story yet. What happens is that a data aggregate like a
  10243.  structure could possibly have extraneous memory associated with it (refer to
  10244.  the sizeof section below), and generating proper code would be rather
  10245.  extravagant, especially to support the relational operators. Furthermore,
  10246.  cross compilation, or rather cross assembly, of such code could produce
  10247.  difficult to track bugs.
  10248.  
  10249.  Member Access
  10250.  
  10251.  Most C programmers I know have no problem understanding C's two member
  10252.  access operators: the . and the ->. The dot allows for the retrieval of a
  10253.  given member within a structure or union, and the arrow allows retrieval
  10254.  from a member referenced by a pointer to a structure or union. However,
  10255.  there seems to be much confusion about the use of other C operators with the
  10256.  member access operators. The ones that seem to cause the most confusion are
  10257.  the & (address of) and the * (indirection) operators. For the novice,
  10258.  something like &*p->x might as well be hieroglyphics. Actually, if you type
  10259.  in the sample listing in Figure 2, many of you, novice and experienced
  10260.  alike, may be surprised to see exactly what this construct and derivations
  10261.  of the construct do.
  10262.  
  10263.  The use of the member access operators is mandated strictly by the operator
  10264.  precedence chart. This chart (one can be found on page 137 of the
  10265.  Microsoft(R) C Compiler (referred to herein as MSC) Version 5.1 Language
  10266.  Reference) makes it clear that both the dot and arrow operators have the
  10267.  highest precedence allowed (along with ( ) and [ ]). Please remember this.
  10268.  As stated in "A Guide to Understanding Even the Most Complex C
  10269.  Declarations," MSJ  (Vol. 3, No. 5), precedence is also an important
  10270.  declaration consideration. Knowing what the highest precedence operators are
  10271.  is one of the key facts C programmers should know (meaning have memorized)
  10272.  since many things in the language depend upon it. Also, from a coding
  10273.  viewpoint, it usually makes reading, writing, and maintenance a snap. I
  10274.  cannot emphasize this point strongly enough.
  10275.  
  10276.  Figure 2 should now make more sense. I'll assume that most of you are fine
  10277.  until at least line 13. If we break down each of the subsequent statements,
  10278.  we should get the parenthesized results found in Figure 3 (you can use it as
  10279.  a guide). Therefore, since pfoo->x means to access a member x of a
  10280.  structure, which is pointed to by pfoo, line 12 says to get the address of
  10281.  the member x, whose structure is being accessed indirectly via pfoo. Note
  10282.  that this does not mean to take the address of pfoo and treat it as a
  10283.  pointer to a structure that contains a member x.
  10284.  
  10285.  Line 14 instructs the compiler to get the contents of the member x of the
  10286.  structure that pfoo points to. This works out fine since x is an int *.
  10287.  However, this does not mean to treat the contents of the pfoo variable as a
  10288.  struct pointer to obtain x. If it meant that, it would be equivalent to
  10289.  pfoo->x, and this obviously isn't the case.
  10290.  
  10291.  Line 16 is a bit interesting to me since it is a case of operators negating
  10292.  each other. For instance, if we were to have a declaration such as int x, we
  10293.  could say *&x. To take this one step at a time, this construct first takes
  10294.  the address of x, which implicitly gets cast into an int *, and then obtains
  10295.  the contents of the value of that address. Well, isn't that simply the same
  10296.  as having just used x itself in the first place? This same form of
  10297.  destructive interference happens on line 16 since it's going to get the
  10298.  contents of the address of pfoo->x (and we know it's going to interpret it
  10299.  in this way because of operator precedence).
  10300.  
  10301.  Finally, although line 17 is constructed somewhat differently from line 16,
  10302.  the end results are the same. However, the reason is admittedly cryptic and
  10303.  requires memorization since it is not immediately intuitive. For instance,
  10304.  in the code in Figure 4, one would expect &*pvar in line 10 to map into
  10305.  &9999 since *pvar is 9999 (which is an error, of course, since you can't
  10306.  take the address of a constant). Instead, if we look at this as though we
  10307.  were reading it as the address of the contents of pvar, then since the
  10308.  contents of pvar is var, its address is &var. This same logic applies to
  10309.  line 17 in Figure 2.
  10310.  
  10311.  I've included lines 15, 18, and 19 in case you decide to investigate the
  10312.  example any further. It might be worth proving to yourself that you
  10313.  understand the above by assuming that the declaration of the member x was
  10314.  changed to char *x. For practice, given this situation, would you be able to
  10315.  change the printf format specifications in Figure 2 to something more
  10316.  appropriate (such as %s or %c)?
  10317.  
  10318.  Figure 2:
  10319.  
  10320.  1    struct s {
  10321.  2    int     y;
  10322.  3    int     *x;
  10323.  4    };
  10324.  5
  10325.  6    struct s foo;
  10326.  7    struct s *pfoo = &foo;
  10327.  8
  10328.  9    main()
  10329.  10    {
  10330.  11    printf("%d\n", foo.x);
  10331.  12    printf("%d\n", pfoo->x);
  10332.  13    printf("%d\n", &pfoo->x);
  10333.  14    printf("%d\n", *pfoo->x);
  10334.  15    printf("%d\n", &foo.x);
  10335.  16    printf("%d\n", *&pfoo->x);
  10336.  17    printf("%d\n", &*pfoo->x);
  10337.  18    printf("%d\n", sizeof(int));
  10338.  19    printf("%d\n", &foo);
  10339.  20    }
  10340.  
  10341.  Figure 3:
  10342.  
  10343.  1    &(pfoo->x)
  10344.  2    *(pfoo->x)
  10345.  3    &(foo.x)
  10346.  4    &(*(pfoo->x)) or simply pfoo->x
  10347.  5    *(&(pfoo->x)) or simply pfoo->x
  10348.  
  10349.  Figure 4:
  10350.  
  10351.  1    main()
  10352.  2    {
  10353.  3    int     var = 9999;
  10354.  4    int     *pvar = &var;
  10355.  5
  10356.  6    printf("%d\n", pvar);
  10357.  7    printf("%d\n", *pvar);
  10358.  8    printf("%d\n", &pvar);
  10359.  9    printf("%d\n", *&pvar);
  10360.  10    printf("%d\n", &*pvar);
  10361.  11    printf("\n");
  10362.  12    printf("%d\n", var);
  10363.  13    printf("%d\n", &var);
  10364.  14    printf("%d\n", *&var);
  10365.  15    printf("%d\n", &*&var);
  10366.  16    printf("%d\n", *&*&var);
  10367.  17    }
  10368.  
  10369.  
  10370.  The sizeof Structure
  10371.  
  10372.  Another rather cryptic and nasty sort of thing to be aware of when using
  10373.  structures is their size. Basically, what you see is not necessarily what
  10374.  you get. But why should you care?
  10375.  
  10376.  Let me explain by using another code sample (see Figure 5). I ran it on an
  10377.  80386 computer and got the results shown. The sizeof(char) and the
  10378.  sizeof(int) print 1 and 4 since this is the size of a byte and a natural
  10379.  word on a 386 (under UNIX and the DOS large model). Note that things seem to
  10380.  be going along smoothly while printing the storage space requirements for
  10381.  chara and charab, but all of a sudden the space for huh seems to be out of
  10382.  whack. This is not a bug in the compiler or in the program. There are parts
  10383.  of C that have been allowed to be classified as undefined behavior and this
  10384.  is one of those areas. In this circumstance, huh takes up 8 bytes instead of
  10385.  5 because intb uses 4 bytes and it must be aligned on a word boundary.
  10386.  Therefore, since chara only uses one byte, the compiler will insert a hole
  10387.  of 3 unnamed and inaccessible bytes after chara to ensure that intb ends up
  10388.  on the right boundary alignment.
  10389.  
  10390.  Some of you may be saying, "Sure that's fine, but why not shift things
  10391.  around so that you don't waste any space?" That doesn't quite work either as
  10392.  can be seen when I printed out the size of huh2. It turns out to be the same
  10393.  size that huh was. Think about it--could this be a coincidence because
  10394.  of an inefficient or lazy compiler or is there some substance to it? The
  10395.  answer must be the latter since an array of type huh2 must be able to be put
  10396.  in storage in such a way that all huh2 intb variables would be word aligned.
  10397.  
  10398.  Going one step further, this means that every structure also has an
  10399.  alignment requirement, a fact that is not readily apparent. Note that I'm
  10400.  not talking about structure tags here since they do not use any execution
  10401.  time memory; however, they will conform to these requirements and produce
  10402.  the correct sizes and offsets if interrogated.
  10403.  
  10404.  In fairness, it is worth mentioning that not all CPUs have this type of
  10405.  alignment requirement. Some enforce alignment only to even addresses, others
  10406.  by multiples of 2, 4, 8, . . . , bytes. And others, even though they would
  10407.  like you to use the suggested alignment, have a lax but less efficient
  10408.  alignment (that is, none). Microsoft C (MSC) Version 5.1 supports this
  10409.  mechanism if you find that there are too many wasted holes in your
  10410.  structures and space is at a premium. You can enable this by using the /Zp
  10411.  command-line switch to CL or by using the #pragma pack statement within your
  10412.  code. These two techniques are explained in the Microsoft C Optimizing
  10413.  Compiler Version 5.1  User's Guide in section 3.3.15.
  10414.  
  10415.  Figure 5:
  10416.  
  10417.  1    #define offsetof(type, identifier) (&(((type *)0)-\
  10418.       >identifier))
  10419.  2
  10420.  3    main()
  10421.  4    {
  10422.  5        struct chara {
  10423.  6        char    chara;
  10424.  7    };
  10425.  8
  10426.  9    struct charab {
  10427.  10        char    chara;
  10428.  11        char    charb;
  10429.  12    };
  10430.  13
  10431.  14    struct huh {
  10432.  15        char    chara;
  10433.  16        int     intb;
  10434.  17    };
  10435.  18
  10436.  19    struct huh2 {
  10437.  20        int     intb;
  10438.  21        char    chara;
  10439.  22    };
  10440.  23
  10441.  24    printf("%d\n", sizeof(char));
  10442.  25    printf("%d\n", sizeof(int));
  10443.  26
  10444.  27    printf("%d\n", sizeof(struct chara));
  10445.  28    printf("%d\n", sizeof(struct charab));
  10446.  29    printf("%d\n", sizeof(struct huh));
  10447.  30    printf("%d\n", sizeof(struct huh2));
  10448.  31
  10449.  32    printf("%d\n", offsetof(struct charab, charb));
  10450.  33    printf("%d\n", offsetof(struct huh, intb));
  10451.  34    printf("%d\n", offsetof(struct huh2, intb));
  10452.  35    }
  10453.  
  10454.  The output for this program on an 80386 computer is:
  10455.  
  10456.  1
  10457.  4
  10458.  1
  10459.  2
  10460.  8
  10461.  8
  10462.  1
  10463.  4
  10464.  0
  10465.  
  10466.  Reading/Writing Structures
  10467.  
  10468.  Very often it is necessary to write a structure to a disk file or RAM or
  10469.  perhaps through a pipe or other interprocess communications facility
  10470.  available under such operating systems as OS/2, XENIX, or UNIX. Because the
  10471.  program "distributing" the structure may not have been compiled with the
  10472.  same packing options or may not even be running on the same machine that is
  10473.  responsible for processing any of the data within the structure, it is
  10474.  advised that you should always create a dummy stub program that will dump
  10475.  the values of the structure before assuming that the data within it must be
  10476.  correct. Remember--alignment restrictions may create holes; holes
  10477.  change the physical layout of the structure. You cannot make assumptions
  10478.  without seeing the source code and/or compile options used.
  10479.  
  10480.  Getting the offsetof
  10481.  
  10482.  It is generally considered bad programming style to include hard-wired
  10483.  constants within your code. And because of potential structure holes, it is
  10484.  neither wise nor portable to use hard-wired constants to represent structure
  10485.  member offsets. For instance, returning to Figure 5, because of the
  10486.  inability to track it down easily, it would not be reasonable for you to
  10487.  keep track of how many bytes into huh that intb was located. Instead you
  10488.  should use the offsetof macro.
  10489.  
  10490.  The offsetof macro is available with most recent compiler releases and can
  10491.  be found in <stddef.h>. It takes two arguments: a type representing a
  10492.  structure and a member of that structure. The result is the offset of the
  10493.  member in bytes from the beginning of the structure. I have included one
  10494.  possible implementation of the macro that will calculate a member's offset;
  10495.  it should work on most machines (see the #define offsetof in Figure 5). The
  10496.  basic idea behind the way the macro works is to pretend the structure begins
  10497.  at address zero [hence the (type*)0] so that any reference to any member
  10498.  from address zero must give its true relative offset in bytes. You are
  10499.  encouraged to type in Figure 5 to experiment with the structures, especially
  10500.  lines 32-34.
  10501.  
  10502.  Forcing Type Alignment
  10503.  
  10504.  Up to this point I have been emphasizing the use of structures. Another
  10505.  popular C construct along these lines is the union construct. It is similar
  10506.  to a struct in its syntactical nomenclature but different in its semantics.
  10507.  Unfortunately, its function within C programs is often misunderstood.
  10508.  
  10509.  Some of you are probably curious about my statement that unions are
  10510.  misunderstood, so let me define what a union is and what a union isn't (or
  10511.  rather, what it isn't supposed to be). A union is a variable that has the
  10512.  ability to hold one, and only one of many different types of named objects
  10513.  (that is, objects with overlapping storage) at a given point in time
  10514.  regardless of the types of those objects. Just think about that for a
  10515.  moment. Ramifications of this definition are as follows:
  10516.  
  10517.  ■    The purpose of a union is to allow for the reuse of a memory location
  10518.  or variable.
  10519.  
  10520.  ■    The memory spaces for all members of the union begin at the same
  10521.  address.
  10522.  
  10523.  ■    The sizeof (a union) = = sizeof (the union's largest member).
  10524.  
  10525.  ■    The value of only one of the union's members may be stored within the
  10526.  union.
  10527.  
  10528.  ■    As a style issue, it is desirable that all the union's members are
  10529.  related to a specific piece of program logic using it.
  10530.  
  10531.  It should now be clear that unions can suffice as a method of forcing type
  10532.  alignment and for redefining types. Redefinition of types is described in
  10533.  the next section, and type alignment is described as follows.
  10534.  
  10535.  Very often in systems programming an operating system resource or request
  10536.  call, a device driver, or a specific hardware device (perhaps using DMA) may
  10537.  require that data transferred to or from it have specific alignment
  10538.  requirements. Luckily C, the de facto systems programming language, can
  10539.  satisfy this requirement via unions.
  10540.  
  10541.  For example, let's make a relatively safe assumption--that most of the
  10542.  time an operating system or device is to be word aligned (which taken one
  10543.  step further on most machines usually means that there should be an even
  10544.  address boundary). Let's also assume that the info is to be passed into a
  10545.  hardware device expecting 6 bytes of information. Simply coding something
  10546.  like char info[6]; may or may not necessarily work since C doesn't guarantee
  10547.  that info will begin on an even address--therefore you've got a 50
  10548.  percent chance of getting it right and even worse odds for tracking down the
  10549.  bug when code is changed 6 months down the road.
  10550.  
  10551.  If you haven't guessed already, the way to ensure that info can be word
  10552.  aligned is through something like:
  10553.  
  10554.  union device_data {
  10555.  int dummy; /* alignment */
  10556.  char    info[6];
  10557.  };
  10558.  
  10559.  Since dummy is word aligned (why?) and the address of each member of a union
  10560.  begins at the same location, info must also be word aligned! Of course, your
  10561.  code never needs to be concerned about dummy again since it has served its
  10562.  purpose. All very elegant, no?
  10563.  
  10564.  Redefinitions: Abuse of Unions?
  10565.  
  10566.  Because C does not enforce one facet of unions, namely that there may only
  10567.  be one object in use at a time, this is left up to the programmer. In other
  10568.  words, you are only supposed to take from a union what you put into it;
  10569.  therefore, if you assign to a particular member, you are only supposed to
  10570.  retrieve from that same member until you assign to another member. The
  10571.  problem here is that C leaves this up to you, at least at a syntactical
  10572.  level. For instance, given the declarations:
  10573.  
  10574.  
  10575.  
  10576.  union example {
  10577.    int     i;
  10578.    double  d;
  10579.    } ex;
  10580.  int     j;
  10581.  
  10582.  probably no C compiler will prohibit you from coding:
  10583.  
  10584.  ex.d = 999.999;
  10585.  j = i;
  10586.  
  10587.  even though you'd be guaranteed that j wouldn't hold anything valid, unless
  10588.  perhaps you were interested in getting a random number. However, if you are
  10589.  careful, this side effect can be turned to occasional advantage, what I'd
  10590.  like to term a nonportable nicety--something I don't recommend that you
  10591.  use, but if you do, I hope that you document the fact and clearly understand
  10592.  what it is you are doing.
  10593.  
  10594.  For example, I was working on a consulting project last year that required
  10595.  use of the Intel(R) RMX operating system. The operating system was running
  10596.  on an 80386, which is a segmented architecture machine. At one point in the
  10597.  project, we needed to obtain some dynamic memory. For some rather obtuse
  10598.  reason, it was decided that none of the standard C run-time routines such as
  10599.  malloc would be appropriate for what we wanted to do. Instead we issued an
  10600.  RMX system call in order to obtain   the memory. The problem we were faced
  10601.  with then was that the call returned an entity called a token, which, every
  10602.  time we called it, turned out to be the beginning of a segment. However, the
  10603.  token only contained a 2-byte segment number and not the 2-byte zero offset
  10604.  as well, and we needed to use the token as a character pointer.
  10605.  
  10606.  The way we resolved the problem was by using code similar to that listed in
  10607.  Figure 6a. However, given the reasons that have already been mentioned, this
  10608.  technique is not guaranteed to work under all C compilers. Furthermore,
  10609.  we're making the assumption that pointer = = int is valid and to make
  10610.  matters worse, our ordering of the segment and offset variables are also
  10611.  system-dependent since byte or word ordering is hardware-dependent. As you
  10612.  can see, all in all it is not a very healthy affair.
  10613.  
  10614.  Knowing this, many of you probably are agreeing but also noticing how easily
  10615.  the conversion routine functioned. Before this looks too enticing, let's
  10616.  look at the preferred method of redefining types: the cast operator. Figure
  10617.  6b has a version of toktoptr that uses casts.
  10618.  
  10619.  This may look rather ugly and the cast code is also making some nonportable
  10620.  assumptions. The best thing I can say is that you must recognize that using
  10621.  unions for redefinitions instead of as reusable storage may work, but the
  10622.  method is simply not valid and is totally nonportable even among compilers
  10623.  on the same machine. The chances are also high that it will produce
  10624.  incorrect code with many of the optimizing compilers currently on the
  10625.  market. The cast, although also system dependent in many ways, is generally
  10626.  the less problematic method, and besides it's "legal" C.
  10627.  
  10628.  The reason I am explaining this is not to teach you a new trick but to make
  10629.  you aware of a bad coding practice and to prepare you for the unexpected if
  10630.  it ever pops up while you are maintaining a piece of code. Also, if you do
  10631.  decide to use this technique, you will know about the problems that can crop
  10632.  up because of it.
  10633.  
  10634.  Figure 6:
  10635.  
  10636.  1    #define getmemory()    20 /* e.g. simulate RSX call */
  10637.  2
  10638.  3    char    *
  10639.  4    toktoptr(token)
  10640.  5    short   token;
  10641.  6    {
  10642.  7    union {
  10643.  8        struct {
  10644.  9        short offset;
  10645.  10        short segment;
  10646.  11        } segoff;
  10647.  12        char    *ptr;
  10648.  13    } convert;
  10649.  14
  10650.  15    convert.segoff.segment = token; /* get segment number */
  10651.  16    convert.segoff.offset = 0;    /* offset is zero within the
  10652.                                             segment */
  10653.  17
  10654.  18    return (convert.ptr);    /* assume segment &
  10655.                      offset overlay ptr
  10656.                                           perfectly */
  10657.  19    }
  10658.  20
  10659.  21    main()
  10660.  22    {
  10661.  23    int token = getmemory(); /* RSX memory call */
  10662.  24    char    *p = toktoptr(token);
  10663.  25
  10664.  26    printf("%d\n", p);
  10665.  27    }
  10666.  
  10667.  Figure 6b:
  10668.  
  10669.  1    #define getmemory()    20 /* e.g. simulate RSX call */
  10670.  2
  10671.  3    char    *
  10672.  4    toktoptr(token)
  10673.  5    short   token;
  10674.  6    {
  10675.  7    return ((char *)(((long)token) <<
  10676.              (sizeof(short) * 8)));
  10677.  8    }
  10678.  9
  10679.  10    main()
  10680.  11    {
  10681.  12    int token = getmemory(); /* RSX memory call */
  10682.  13    char    *p = toktoptr(token);
  10683.  14
  10684.  15    printf("%d\n", p);
  10685.  
  10686.  16    }
  10687.  
  10688.  Unions in General
  10689.  
  10690.  A more general use of unions is for building coded records. Since a union is
  10691.  capable of declaring a valid identifier, it may appear within a structure
  10692.  (and vice versa). An example of this occurs in Figure 7, which illustrates a
  10693.  case in which a certain record is obtained, perhaps through a communications
  10694.  line, and needs to be processed. Each record has a code recordtype
  10695.  signifying what shape it is to take on so that it can be directed to the
  10696.  appropriate case of the switch statement that knows about it and the names
  10697.  of its members.
  10698.  
  10699.  This is a very elegant and easily maintainable feature of C. I was able to
  10700.  create a data structure that did not have to use any unnecessary data space
  10701.  nor did I need to resort to playing any games with pointers to multiple
  10702.  structures to be able to use the correct type structure.
  10703.  
  10704.  Note that contrary to what  was discussed in the preceding section, you can
  10705.  reuse the first int of codedrecord's members without harm. This is an
  10706.  extension as a special case. That is, if any initial segment of overlapping
  10707.  structures within a union is the same, a write to one of the objects can be
  10708.  followed by a read from another object. For instance, if the logistics
  10709.  behind the construction of type1 and type2 data structures were similar, it
  10710.  would be valid to store a value in a and then use c with absolutely no ill
  10711.  effects because a and c are of the same type and located at the same
  10712.  offsets. As a style issue it may even be better to take a, c, and e out of
  10713.  union and make it a single entity in genericrecord. However, this
  10714.  would depend entirely on the logical connection between these
  10715.  identifiers as well as the logic behind the code that uses them.
  10716.  
  10717.  One last point about unions: though the draft proposal of ANSI C now allows
  10718.  for union initialization to occur in C code, the initialization must be
  10719.  represented as a constant expression and the initialization expression can
  10720.  only initialize the first member of the union. This is something to be aware
  10721.  of if your compiler does a conversion to a different type and quietly
  10722.  performs the assignment, perhaps by truncation. You may have to swap the
  10723.  order of the variables in the union in some cases to ensure proper
  10724.  initialization.
  10725.  
  10726.  Figure 7:
  10727.  
  10728.  1    #include <stdio.h>
  10729.  2
  10730.  3    union codedrecords {
  10731.  4    struct {
  10732.  5        int     a;
  10733.  6        float   b;
  10734.  7    } type1;
  10735.  8    struct {
  10736.  9        int     c;
  10737.  10        long    d;
  10738.  11    } type2;
  10739.  12    struct {
  10740.  13        int     e;
  10741.  14        short   f;
  10742.  15    } type3;
  10743.  16    };
  10744.  17
  10745.  18    struct genericrecord {
  10746.  19    int     recordtype;
  10747.  20    union codedrecords cr;
  10748.  21    };
  10749.  22
  10750.  23    struct genericrecord gr;
  10751.  24
  10752.  25    main()
  10753.  26    {
  10754.  27    /* ... */
  10755.  28
  10756.  29    switch(genericrecord.recordtype) {
  10757.  30    case TYPE1:
  10758.  31        /* code that uses genericrecord.cr.type1.? */
  10759.  32        break;
  10760.  33
  10761.  34    case TYPE2:
  10762.  35        /* code that uses genericrecord.cr.type2.? */
  10763.  36        break;
  10764.  37
  10765.  38    case TYPE3:
  10766.  39        /* code that uses genericrecord.cr.type3.? */
  10767.  40        break;
  10768.  41
  10769.  42    default:
  10770.  43        fprintf(stderr, "Invalid Record Type!!!");
  10771.  44        exit (1);
  10772.  45        break;
  10773.  46    }
  10774.  47
  10775.  48    /* ... */
  10776.  49    }
  10777.  
  10778.  The typedef
  10779.  
  10780.  Before going into some explanations and examples of typedefs and structures,
  10781.  let me get two common misconceptions about typedefs and one source of
  10782.  confusion out of the way. First, even though the typedef keyword is
  10783.  syntactically categorized as a storage class specifier, it is a misnomer and
  10784.  does not allocate any execution time storage. It is only categorized as such
  10785.  for notational convenience.
  10786.  
  10787.  Second and more pertinent to our discussion is that typedefs do not create
  10788.  or define a new type, as the keyword may imply. What they do is allow the
  10789.  programmer to create a new name for a base or derived type that already
  10790.  exists. In other words, a typedef allows you to create a synonym for the
  10791.  type.
  10792.  
  10793.  Furthermore, the synonym is placed into the general name space (an area used
  10794.  to categorize identifiers) of the compiler's symbol table. The general name
  10795.  space holds function and most variable and enumeration names as well. The
  10796.  end result is that typedef names are practically no different from any
  10797.  others. That is to say, they serve to lock into a name, much like a struct
  10798.  or union tag does. In this way, typedef names can be used to classify
  10799.  identifiers declared with them.
  10800.  
  10801.  Finally, an additional source of confusion with typedef is that you are not
  10802.  allowed to place any storage class specifiers within the type name being
  10803.  created during a typedef statement. This is because you are not allowed to
  10804.  have more than one storage class specifier within any of your declarations.
  10805.  Some would say this is a blessing in disguise since hiding storage class
  10806.  information within a typedef could result in code that is harder to maintain
  10807.  and write than it should be. In other words, it wouldn't be directly obvious
  10808.  from the declaration of a variable based on the typedef what all its
  10809.  attributes are. It's a thin line since I'm not sure what an object-oriented
  10810.  programmer would say about this abstraction.
  10811.  
  10812.  While on this subject, one more thing to remember is that while you may not
  10813.  have storage class specifiers associated with the typedef name, you can use
  10814.  the const and volatile type qualifiers. However, if you use incremental
  10815.  typedefs, only one appearance of a given qualifier is allowed to be applied
  10816.  to the previous typedef declarator. I could be wrong, but I suspect that
  10817.  many compilers would have a problem issuing a proper diagnostic error about
  10818.  this.
  10819.  
  10820.  Allocating Structures
  10821.  
  10822.  There are two common techniques for allocating a structure. They involve the
  10823.  use of #define and typedef. Both can be used to achieve similar goals;
  10824.  however, their syntax, and amazingly their semantics, are very different. If
  10825.  possible the allocation should be performed via typedef. Let's see why.
  10826.  
  10827.  Since K&R never elaborated upon the use of typedef, and since many of the
  10828.  earlier non-AT&T(R) C compilers usually didn't support it (possibly for that
  10829.  reason), until the past few years typedefs were either ignored or
  10830.  misunderstood. The usual method for allocating a structure was to use the
  10831.  #define directive. For instance, before the void keyword became popular on
  10832.  all compilers, it was usually suggested to include
  10833.  
  10834.  #define void int
  10835.  
  10836.  in all programs, usually via a programmer supplied include file, say
  10837.  mydefs.h, that functioned similarly to the macros currently found in
  10838.  stddef.h. However, even though this worked without a hitch (try compiling
  10839.  the code listed in Figure 8), it will create problems with only slightly
  10840.  more complicated type specifications. Do not get off on the wrong track by
  10841.  using old code as an example.
  10842.  
  10843.  The problems appear because #define is concerned only about textual
  10844.  substitutions of strings or tokens of strings. And this is fine since that
  10845.  is the duty of the preprocessor. However, it doesn't know or care about C
  10846.  syntax. The preprocessor is only responsible for accomplishing the text
  10847.  substitutions; as long as it is fed a valid C program, it must emit a valid
  10848.  C program. On the other hand, since the compiler does classify typedef
  10849.  names, it can and will enforce type checking of expressions involving
  10850.  typedefs.
  10851.  
  10852.  Figure 8:
  10853.  
  10854.  A
  10855.  1    #define v int    /* don't use void in this example since
  10856.                      it's a keyword */
  10857.  2
  10858.  3    main()
  10859.  4    {
  10860.  5    int x;
  10861.  6    v   y;
  10862.  7
  10863.  8    x = y;
  10864.  9    }
  10865.  
  10866.  B
  10867.  1    typedef int v;    /* don't use void in this example since
  10868.                      it's a keyword */
  10869.  2
  10870.  3    main()
  10871.  4    {
  10872.  5    int x;
  10873.  6    v   y;
  10874.  7
  10875.  8    x = y;
  10876.  9    }
  10877.  
  10878.  If we use some of the examples presented by K&R, whose interpretation is
  10879.  more or less left as an exercise for the reader (even in the new revised
  10880.  second edition), we can investigate why #define will fail. Using their
  10881.  declaration:
  10882.  
  10883.  typedef char  * String;
  10884.  
  10885.  the equivalent #define would be:
  10886.  
  10887.  #define MAXLINES (5)
  10888.  #define String  char *
  10889.  
  10890.  If we also use their sample invocations of String:
  10891.  
  10892.  String p,
  10893.  lineptr[MAXLINES],
  10894.  alloc(int);
  10895.  
  10896.  a first glance might not indicate any problems. However, if we follow
  10897.  through the text substitutions, the line gets passed (at least as a
  10898.  transparent step) to the C compiler as:
  10899.  
  10900.  char * p, lineptr[5], alloc(int);
  10901.  
  10902.  certainly not what was desired.
  10903.  
  10904.  What happened is that only p was declared as a char *. And lineptr and alloc
  10905.  were declared as an array of char and a function returning char,
  10906.  respectively. We were trying to obtain:
  10907.  
  10908.  char *p, *lineptr[5], *alloc(int);
  10909.  
  10910.  Besides the #define problem, this also points out a pitfall of using a
  10911.  multideclarator declaration. You were warned of this in "A Guide to
  10912.  Understanding Even the Most Complex C Declarations."
  10913.  
  10914.  As another simple case in which #define could not possibly work, consider:
  10915.  
  10916.  
  10917.  
  10918.  typedef int * array20[20];
  10919.  
  10920.  There's just no way to coerce it to produce:
  10921.  
  10922.  array20  samplearray;
  10923.  
  10924.  Finally, relating all this back to structs and unions, there are also the
  10925.  type checking capabilities of the compiler that are a concern to us. For
  10926.  instance, using K&R's Treenode example, an equivalent define (ignoring
  10927.  Treeptr for our purposes) would be:
  10928.  
  10929.  #define Treenode struct {\
  10930.  char  *word;\
  10931.  int   count;\
  10932.  }
  10933.  
  10934.  Invocations of this might appear as follows:
  10935.  
  10936.  Treenode  tn1, tn2;
  10937.  Treenode  tn3, *ptn;
  10938.  
  10939.  However, although tn1 and tn2 are assignment and member compatible, they
  10940.  have nothing to do with other declarations  that might use Treenode even if
  10941.  they are pointers such as ptn. Therefore:
  10942.  
  10943.  tn1 = tn2;
  10944.  
  10945.  tn1.count = tn2.count;
  10946.  
  10947.  are fine, but:
  10948.  
  10949.  tn1 = tn3;
  10950.  ptn = &tn1;
  10951.  
  10952.  are undoubtedly syntax errors.
  10953.  
  10954.  To make matters worse, if we use the style constraint of one declarator per
  10955.  declaration:
  10956.  
  10957.  Treenode  tn1;
  10958.  Treenode  tn2;
  10959.  Treenode  tn3;
  10960.  Treenode  *ptn;
  10961.  
  10962.  none of these have the ability to have anything to do with the
  10963.  others--if we perform the preprocessor substitution, we get four
  10964.  unnamed yet distinct structure tags. The compiler doesn't care that they
  10965.  might all look the same. Every invocation of Treenode will create a new
  10966.  structure.
  10967.  
  10968.  If instead we (properly) use a typedef:
  10969.  
  10970.  typedef struct {
  10971.  char  * word;
  10972.  int count;
  10973.  } Treenode;
  10974.  
  10975.  all the executable statements above are valid. The compiler only creates one
  10976.  reference to the struct in the symbol table and after that everything falls
  10977.  into place including type checking. All is now syntactically sound. Since a
  10978.  typedef is C code, all the Treenode invocations will have the same type.
  10979.  
  10980.  Proper Perspective
  10981.  
  10982.  The basics of structures and unions are well defined and most programmers
  10983.  are capable of using them with reasonably good results. However using them
  10984.  to their full capacity and in a portable way requires a bit more knowledge
  10985.  than commonly available. This also applies to typedef, which has often been
  10986.  either neglected or misunderstood (typedef becomes very important under the
  10987.  OS/2 and Presentation Manager programming environments--Ed.). Since a
  10988.  structure is the main data construct in C (as well as in many other
  10989.  languages), now that you have these facts under your belt, you should have a
  10990.  better understanding of the C language. You can use it to your advantage in
  10991.  a wise, efficient, and portable manner.
  10992.  
  10993.  Skipping over any detail of a language specification is a mistake. Certainly
  10994.  some things are useless and awkward, but it's disappointing to think that
  10995.  many programmers avoid parts of languages simply because they are somewhat
  10996.  complicated. Though C is many times a very terse language and often cryptic,
  10997.  an understanding of its more subtle and complex points opens up C's unique
  10998.  power.
  10999.  
  11000.  My own experience with C,  as well as that of other programmers, clearly
  11001.  shows that continuing to use it while not understanding it is not helpful.
  11002.  This usually results in slower development and mistakes that will be costly.
  11003.  If you stick with it and put in that extra energy to understand its advanced
  11004.  syntax and especially the underlying philosophy, you will be able to tap
  11005.  into its most powerful and advanced features.
  11006.  
  11007.  
  11008.  Whitewater's Actor : An Introduction to Object-Oriented Programming Concepts
  11009.  
  11010.   Zack Urlocker
  11011.  
  11012.  Object-oriented programming techniques are not new, but they are becoming
  11013.  more popular as programmers tackle increasingly complex projects.
  11014.  Object-oriented programming can help simplify the development of elaborate
  11015.  programs by breaking them down into logical objects that manage their own
  11016.  behavior and hide internal complexity. Windowing applications in particular
  11017.  are easier to develop and maintain if object-oriented programming techniques
  11018.  are used. Although object-oriented programming is best done in a pure
  11019.  object-oriented language, such as Actor(R) or Smalltalk, it can also be used
  11020.  in other languages.
  11021.  
  11022.  This article provides an overview of object-oriented programming,
  11023.  demonstrating how it can simplify the development of Windows programs. If
  11024.  you haven't done much programming in Microsoft(R) Windows, some knowledge
  11025.  about object-oriented programming can help you understand how Windows works.
  11026.  Most of the sample code is taken from PC-Project, a critical path
  11027.  project management program that I wrote in Actor.
  11028.  
  11029.  PC-Project lets you model a real-world project by creating milestones
  11030.  and tasks, assigning times, and determining the critical path of the
  11031.  project. The critical path shows which activities, if delayed, will cause a
  11032.  delay in the overall completion of the project. PC-Project also lets
  11033.  you allocate resources and costs to tasks and create a Gantt time-line chart
  11034.  of the project. PC-Project is available with complete source code from
  11035.  various Bulletin Board systems or directly from the author. Figure 1 shows
  11036.  the PC-Project application running under Windows.
  11037.  
  11038.  What Is Object-Oriented ?
  11039.  
  11040.  In traditional procedural languages like C or Pascal, the programmer defines
  11041.  data structures and writes functions and procedures to operate on the data.
  11042.  Although normally a correspondence exists between which functions operate on
  11043.  which types of data, most procedural languages offer no formal support for
  11044.  this correspondence; it is entirely the programmer's responsibility to
  11045.  manage such an abstraction.
  11046.  
  11047.  In an object-oriented lan-guage, both data and operations that work with
  11048.  that data are combined into a single logical unit known as an object.
  11049.  Dividing a program into objects encompassing both data and operations makes
  11050.  the program more closely represent the logical design that is being
  11051.  implemented. As a result, object-oriented programs are generally easier to
  11052.  understand and maintain than procedural programs.
  11053.  
  11054.  Object-oriented program-ming encourages the creation of abstract data types;
  11055.  that is, the implementation of an object is referred to abstractly and is
  11056.  encapsulated by high-level operations. Objects have a clear division between
  11057.  public protocol and private imple-mentation. For example, we might have a
  11058.  stack object that defines a public protocol based on the push and pop
  11059.  operations. The stack may be implemented as an array with variables that
  11060.  maintains the first and last positions, but this representation would be
  11061.  considered private. By adhering to the public protocol, we can change the
  11062.  implementation of stacks, say, to linked lists, without having to rewrite
  11063.  any of our code. Figure 2 illustrates a stack object and the separation of
  11064.  public protocol and private implementation.
  11065.  
  11066.  Programming in an object-oriented language involves creating objects and
  11067.  sending them commands or messages to do things. For example, we can create a
  11068.  window with the caption Sample and then show it on the screen. In Actor,
  11069.  this is done using the messages shown below:
  11070.  
  11071.  /* create it */
  11072.  W := defaultNew(
  11073.      Window, "Sample");
  11074.  show(W, 1); /* display it */
  11075.  close(W);  /* close it */
  11076.  
  11077.  In this case Window is a predefined Actor class or type of object already in
  11078.  the system. Therefore dozens of lines of code are eliminated that would
  11079.  otherwise have to be written in C to accomplish the same thing without
  11080.  affecting performance. Window objects have private data that manage their
  11081.  location, size, caption, parent, handle, and so on. Window objects know how
  11082.  to create themselves, position themselves on the screen, resize, close, and
  11083.  so on, as part of their public protocol. Thus, the statement close(W); is a
  11084.  message being sent to the W window to close itself.
  11085.  
  11086.  Although it may seem that messages are the same as function calls in other
  11087.  languages, they are not. The receiver of a message, which is the first
  11088.  parameter after the parenthesis, determines how to respond. Different
  11089.  classes of objects can respond to the same message in different ways, a
  11090.  language characteristic known as polymorphism.
  11091.  
  11092.  For example, if W were a member of the ProjWindow class that received a
  11093.  close message, it would first check to make sure that the current project
  11094.  was saved. If W were a member of the GanttWindow class, it would inform its
  11095.  parent window that it was closing. In fact, W doesn't even need to be a
  11096.  window at all. Other objects, such as files or communication channels, could
  11097.  respond to a close message.
  11098.  
  11099.  Polymorphism is achieved in Actor by defining a method, with the
  11100.  corresponding message name, for the class. A method definition is similar to
  11101.  a function definition in other languages. Polymorphism allows you to write
  11102.  more general, reusable code, since you don't have to worry about what types
  11103.  of objects you are dealing with, as long as they follow the same public
  11104.  protocol. You can let the objects themselves manage the details of
  11105.  implementation.
  11106.  
  11107.  Inheritance
  11108.  
  11109.  Objects are organized hierarchically in classes. Most object-oriented
  11110.  languages include classes for things like arrays, files, strings, stacks,
  11111.  and queues. In Actor there are also predefined classes for dealing with such
  11112.  Windows entities as text windows, dialog boxes, and scroll bars.
  11113.  
  11114.  We can create new subclasses that inherit all the character-istics of an
  11115.  existing class. For example, in PC-Project the ProjWindow class
  11116.  descends from the Window class and adds to it functionality related to
  11117.  project management. Because of this inheritance capability, classes are much
  11118.  more powerful than data types in other lan-guages. The advantage is that all
  11119.  the generic windowing capabilities, like resizing, displaying, and dragging
  11120.  work properly without your having to write or test a single line of code.
  11121.  Using inheritance you focus on those parts of the program that are
  11122.  application-specific instead of constantly "reinventing the window," so to
  11123.  speak.
  11124.  
  11125.  Inheritance encourages the development of small, reusable classes that
  11126.  become building blocks for more sophisticated classes. This approach results
  11127.  in less code to maintain and test and more rapid development from prototype
  11128.  to final application.
  11129.  
  11130.  In PC-Project I used inheritance to group the characteristics that are
  11131.  common to the dialog boxes used for editing activities and for the
  11132.  activities themselves. Figure 3 describes the classes in PC-Project.
  11133.  Figure 4 shows how they are related in the class tree.
  11134.  
  11135.  How does object-oriented programming work with procedural languages? Purists
  11136.  maintain that object-oriented programming is possible only in a late-bound
  11137.  language that has a class facility and inheritance. However, object-oriented
  11138.  design techniques are applicable in many languages. For example, both Ada
  11139.  and Modula-2 include facilities that allow the creation of abstract data
  11140.  types. You can also add object-oriented extensions to C by using a C++
  11141.  preprocessor. Any program can be designed, if not implemented, as logical
  11142.  objects that encompass data and operations with a clear separation of public
  11143.  protocol and private implementation.
  11144.  
  11145.  Many programmers are pleasantly surprised to find that object-oriented
  11146.  languages encourage them to use techniques they have been faking for years
  11147.  in other languages. Through the rest of this article I encourage you to
  11148.  consider how object-oriented techniques could be used in your current
  11149.  language.
  11150.  
  11151.  Figure 3:
  11152.  
  11153.  ProjWindow    window that can display a PERT diagram
  11154.  GanttWindow    window that can display a Gantt chart
  11155.  
  11156.  ActivDialog    formal class of dialog box for activities
  11157.  MStoneDialog    dialog box for editing Milestones
  11158.  TaskDialog    dialog box for editing Tasks
  11159.  PERTDialog    dialog box for editing PERTTasks
  11160.  
  11161.  Network     generic network of nodes with a start and end
  11162.  Node    generic node capable of connecting itself
  11163.  
  11164.  Project    network that knows the critical path method
  11165.  Activity    node with an earlyStart and lateFinish
  11166.  Milestone    an activity that uses no time or resources
  11167.  Task    an activity that has time, resources, and cost
  11168.  PERTTask    Task where the time is estimated by PERT
  11169.  
  11170.  Resource    used by a task; has a name and cost
  11171.  
  11172.  Windows
  11173.  
  11174.  Windows, like object-oriented languages, operates on a message-passing
  11175.  paradigm. Windows is an event-driven system, meaning that programs respond
  11176.  to events that the user or other programs initiate. These events correspond
  11177.  to actions like pressing a key, clicking the mouse, or selecting a menu
  11178.  item. Whenever an event occurs, Windows sends a message to notify the
  11179.  program.
  11180.  
  11181.  More specifically, when the user presses a key, for example, Windows sends a
  11182.  WM_KEYDOWN message with the virtual key code of the key that was pressed.
  11183.  The "WM" is mnemonic for "Windows message." Other Windows messages are
  11184.  WM_COMMAND (indicating the user selected a menu command), WM_LBUTTONDOWN
  11185.  (the user clicked the left mouse button), WM_VSCROLL (the user clicked in
  11186.  the vertical scroll bar), and WM_PAINT (Win-dows wants the window to redraw
  11187.  itself).
  11188.  
  11189.  Windows messages are always sent with two parameters to convey additional
  11190.  information. These are known as the word parameter, or wParam, and the long
  11191.  parameter, or lParam. The wParam contains a 16-bit word value; the lParam
  11192.  sometimes contains a 32-bit long pointer to other data.
  11193.  
  11194.  If an application has multiple windows, as PC-Project does, the
  11195.  Windows messages are sent to the appropriate window. For example, if you
  11196.  press the F1 function key while the Gantt window has the focus, a message is
  11197.  sent to that window; the main window is not informed. Making windows
  11198.  responsible only for their own events simplifies application development.
  11199.  
  11200.  Actor Objects
  11201.  
  11202.  The relationship between Actor objects and Windows entities is similar to
  11203.  the relationship between file variables and files in most high-level
  11204.  languages. For example, in Pascal, you can declare a variable of type File.
  11205.  To use the variable, however, you must assign it the name of an actual disk
  11206.  file. The file variable is an abstraction of the physical file on disk. In
  11207.  the same way, Actor objects belonging to classes like Window and Dialog are
  11208.  abstractions of underlying areas of memory managed by Windows.
  11209.  
  11210.  Although both Actor and Windows send messages, the messages are processed
  11211.  separately. Unlike Windows messages, Actor messages are not queued at all
  11212.  and are therefore very efficient. The main function in a Windows
  11213.  application, called WinMain, normally includes a very short loop that
  11214.  translates and dispatches Windows messages. The application must also define
  11215.  a WndProc function that processes the messages.
  11216.  
  11217.  From an object-oriented perspective, it is the window itself that responds
  11218.  to the messages. After all, a window is not just a data structure, it is
  11219.  both the data and the functionality. Thus, Actor manages the WinMain and
  11220.  WndProc functions, the Windows message queue, and other low-level details.
  11221.  
  11222.  Actor automatically translates Windows messages into equivalent Actor
  11223.  messages, enabling you to process all messages in the same way. The Actor
  11224.  classes Window and WindowsObject define many high-level messages that hide
  11225.  the generic details of Windows programming and allow the programmer to
  11226.  concentrate on application specific behavior. Figure 5 shows the flow of
  11227.  messages between Windows and Actor objects.
  11228.  
  11229.  The WM_PAINT method defined in the Actor class Window, automatically locks
  11230.  down an area of memory known as a display context used for redrawing. It
  11231.  then calls the Windows function BeginPaint, sends an Actor paint message,
  11232.  calls the EndPaint function, and lastly frees the memory used for drawing.
  11233.  
  11234.  The WM_PAINT method defined for class Window is shown in Figure 6. Since
  11235.  this method is inherited by all descendants of the class Window, they only
  11236.  need to define a higher-level paint method that knows how to redraw the
  11237.  contents of the window. The Actor paint message will be sent whenever
  11238.  Windows sends a WM_PAINT message.
  11239.  
  11240.  In PC-Project, the ProjWindow class defines a paint method to redraw a
  11241.  network or PERT diagram of the project. The paint method, shown in Figure 7,
  11242.  includes no calls to Windows functions to manage the display context, since
  11243.  this job will be handled by the WM_PAINT method of the Window class.
  11244.  
  11245.  The paint method loops through all the nodes in the project, and if a node
  11246.  is visible, it sends a draw message to the node. To determine where to draw
  11247.  a node, a pos message is sent to the node to get its logical display
  11248.  position. For example, the first node in a project is at the logical display
  11249.  position (0,0). This position is converted into the Windows coordinate
  11250.  (10,30) by sending a displayToWindow message to the window, with the logical
  11251.  display position as an argument.
  11252.  
  11253.  Notice that while it is the node's responsibility to manage its logical
  11254.  location, it is the window's responsibility to determine if the node is
  11255.  visible and convert the logical display location into its own Windows
  11256.  coordinates. Again, the goal in object-oriented programming is to let the
  11257.  objects manage their own behavior as much as possible.
  11258.  
  11259.  Figure 6:
  11260.  
  11261.  /* Trap MS-Window's message to paint self, a Window.
  11262.     Sends a paint(self) message with the display context.
  11263.  
  11264.     self is a Window or ancestor of class Window.
  11265.     wParam and lParam arguments are ignored.
  11266.     hDC, hPS, lpPS are local variables.
  11267.       hDC : handle to display context for drawing
  11268.       hPS : handle to paint struct
  11269.       lpPS : long pointer to locked down paint struct
  11270.  */
  11271.  Def  WM_PAINT(self, wParam, lParam | hDC, hPS, lpPS)
  11272.  {
  11273.     hPS := asHandle(paintStruct);        /* get hPS */
  11274.     lpPS := globalLock(hPS);             /* lock down mem */
  11275.     hDC := Call BeginPaint(hWnd, lpPS);  /* get hDC */
  11276.     paint(self, hDC);                    /* send paint msg */
  11277.     Call EndPaint(hWnd, lpPS);           /* done painting */
  11278.     globalUnlock(hPS);                   /* free memory */
  11279.     ^0;                                  /* ok, return 0 */
  11280.  }
  11281.  
  11282.  Figure 7:
  11283.  
  11284.  /* Respond to MS-Windows messages to paint the window as a PERT
  11285.     (network) diagram.
  11286.     Draw each visible node in its proper position.
  11287.     Display the name and any other info required.
  11288.     Then draw the lines to connect the outputs of the node.
  11289.  
  11290.     self is the ProjWindow that receives the message.
  11291.     hDC, a handle to a display context, is sent as an arg.
  11292.     wPoint, x, y are local variables.
  11293.     aNode is the temporary loop variable for the do message.
  11294.  */
  11295.  Def  paint(self, hDC | wPoint, x, y)
  11296.  {
  11297.    do(nodes(project),
  11298.      {using(aNode)
  11299.  
  11300.       wPoint := displayToWindow(self, pos(aNode));
  11301.       x := x(wPoint);        /* horiz windows posn */
  11302.       y := y(wPoint);        /* vert windows posn */
  11303.  
  11304.      if visible(self, aNode)
  11305.        draw(aNode, self, x, y, hDC);
  11306.        drawTextInfo(self, aNode, x, y, hDC);
  11307.      endIf;
  11308.  
  11309.      /* always draw connections since they may be visible */
  11310.  
  11311.      drawConnections(self, aNode,x,y, getOutputs(aNode),hDC);
  11312.    });
  11313.  }
  11314.  
  11315.  Windows Messages
  11316.  
  11317.  In addition to Windows sending messages, like the WM_PAINT message described
  11318.  above, window objects can send messages to other windows or even to
  11319.  themselves. For example, in PC-Project if the user presses the up
  11320.  arrow, the window will scroll up as necessary. This is done by trapping the
  11321.  WM_KEYDOWN Windows message and sending a WM_VSCROLL Windows message to the
  11322.  window.
  11323.  
  11324.  When a scroll message is sent, the wParam argument indicates the scroll
  11325.  direction, defined by a constant such as SB_LINEUP or SB_LINEDOWN. (The "SB"
  11326.  is mnemonic for "scroll bar.") In the case of a scroll message, the lParam
  11327.  argument is ignored, so by convention we send a long zero, 0L.
  11328.  
  11329.  To send a scroll message to a window using C, we call the sendMessage
  11330.  function with a handle to the window that will receive the message, the
  11331.  Windows message constant, and the wParam and lParam arguments, thus:
  11332.  
  11333.  sendMessage(W.hWnd,
  11334.            WM_VSCROLL,
  11335.            SB_LINEUP,
  11336.            0L);  /* C */
  11337.  
  11338.  Since Actor automatically translates Windows messages into Actor messages of
  11339.  the same name, there is no need to call the sendMessage function (though you
  11340.  could if you wanted to). Instead the scroll message can be sent directly to
  11341.  the W window:
  11342.  
  11343.  WM_VSCROLL(W, SB_LINEUP,
  11344.           0L);  /* Actor */
  11345.  
  11346.  Actor will then call the sendMessage function with appropriate arguments.
  11347.  Note that in Actor the internal representation of the window and its handle
  11348.  are hidden; the window object is responsible for managing its data and
  11349.  responding to all messages. Of course, you could define a higher-level
  11350.  method, perhaps called scrollUp, that would hide the details of the
  11351.  WM_VSCROLL message and SB_LINEUP constant.
  11352.  
  11353.  Once you understand how Windows sends messages in response to user events, a
  11354.  basic principle of object-oriented programming should become clear: objects
  11355.  are like event-driven data structures. This similarity makes programming for
  11356.  a windowing environment with an object-oriented language very natural.
  11357.  
  11358.  A Keyboard Interface
  11359.  
  11360.  Messages can easily be used to create a keyboard interface. Although
  11361.  PC-Project uses the mouse extensively, I wanted to make sure that the
  11362.  user could use the keyboard if he/she preferred to. Windows provides
  11363.  automatic support for keyboard menu commands, but what about scrolling and
  11364.  selecting activities? Normally these are done by clicking the mouse in a
  11365.  scroll bar or clicking on an activity in the project window.
  11366.  
  11367.  I was able to trap all key presses and simulate mouse actions in the main
  11368.  window of PC-Project by defining a WM_KEYDOWN method for the
  11369.  ProjWindow class. For example, if the user presses the up arrow, a
  11370.  WM_VSCROLL message is sent. If he/she presses the F2 function key or Enter,
  11371.  a WM_LBUTTONDOWN message is sent.
  11372.  
  11373.  After years of conditioning with Lotus(R) 1-2-3(R), many PC users
  11374.  intuitively press the slash (/) key to enter commands. Even Microsoft Excel,
  11375.  a model Windows program, employs this method as an alternative to the
  11376.  Windows user interface. Similarly, programs such as Microsoft Word use the
  11377.  Esc key to bring up the command menu. I wanted PC-Project to
  11378.  accommodate all these different user interfaces. But how?
  11379.  
  11380.  Windows allows us to define accelerator keys that trigger WM_COMMAND
  11381.  messages rather than WM_KEYDOWN messages. The accelerator table is written
  11382.  as part of a resource script file and is separate from the application's
  11383.  source code.
  11384.  
  11385.  I defined the slash and Esc keys as accelerators that would send a
  11386.  WM_COMMAND Windows message with the wParam argument as the constant
  11387.  PW_COMMAND_MODE. The WM_COMMAND message is trapped so that when wParam has
  11388.  the value PW_COMMAND_MODE, an Actor commandMode message is sent. If the keys
  11389.  are defined as accelerators, rather than trapped as in the WM_KEYDOWN
  11390.  method, they work in all windows of the application and there is no need to
  11391.  remove the keystroke from the input buffer.
  11392.  
  11393.  But the question remained, how to respond to the commandMode message in
  11394.  order to activate the menu bar without selecting any item? To answer this
  11395.  question, I used the SPY.EXE utility of the Microsoft Windows Software
  11396.  Development Kit to see what messages Microsoft Excel sends when the slash
  11397.  key is pressed. It sends a WM_SYSCOMMAND message with the constant F100H.
  11398.  
  11399.  The source code in Figure 8 shows how the resources are defined as well as
  11400.  how the WM_COMMAND and commandMode messages are trapped. You can use these
  11401.  techniques to add a keyboard user interface to your own Windows programs in
  11402.  Actor or C.
  11403.  
  11404.  The WM_COMMAND method is written in essentially the same style as would be
  11405.  used in C, by employing a lengthy case statement that determines what action
  11406.  to take. Although this approach works, it is not very object-oriented. A
  11407.  more typical approach in Actor would be to write a method called command
  11408.  that eliminates the case statement by using a lookup table known as a
  11409.  dictionary. Dictionary is a predefined Actor class that allows array-like
  11410.  access to elements of a collection using arbitrary keys.
  11411.  
  11412.  For example, we would create a dictionary called actions that used as its
  11413.  key the values of the wParam argument and as values the literal message to
  11414.  be sent. The pound symbol (#) is used to specify a literal symbol name. The
  11415.  dictionary would be initialized as shown in Figure 9. The command method
  11416.  becomes much shorter; we simply look up the message in the actions
  11417.  dictionary and perform it. Compare this method, as shown in Figure 10, to
  11418.  the one shown in Figure 8.
  11419.  
  11420.  Writing the command method in this way makes it much easier for descendant
  11421.  classes to modify their behavior without having to redefine the entire
  11422.  method. They only have to add or change elements in the actions table. This
  11423.  approach leads to much better code reusability than is possible with the
  11424.  procedural approach.
  11425.  
  11426.  Figure 8:
  11427.  
  11428.  /* initialize the actions dictionary */
  11429.  
  11430.  actions := new(Dictionary, 10);
  11431.  actions[PW_FILE_OPEN] := #fileOpenAs;
  11432.  actions[PW_FILE_NEW] := #fileNew;
  11433.  
  11434.                  ■
  11435.                  ■ <other cases omitted>
  11436.                  ■
  11437.  
  11438.  actions[PW_HELP] := #help;
  11439.  actions[PW_COMMAND_MODE] := #commandMode;
  11440.  
  11441.  Figure 9:
  11442.  
  11443.  PROJECT.RC
  11444.  
  11445.  ; Accelerators are used to enhance the keyboard interface
  11446.  ; note: cursor keys are not defined as accelerators and are
  11447.  ; trapped in the WM_KEYDOWN for ProjWindow
  11448.  ;
  11449.  
  11450.  #define VK_SLASH    191                 ; For Lotus-like commands
  11451.  
  11452.  PC-Project ACCELERATORS
  11453.  BEGIN
  11454.    VK_SLASH, PW_COMMAND_MODE, VIRTKEY
  11455.    VK_ESC, PW_COMMAND_MODE, VIRTKEY
  11456.    VK_F1, PW_HELP, VIRTKEY
  11457.    "^o", PW_FILE_OPEN
  11458.    "^n", PW_FILE_NEW
  11459.  END
  11460.  
  11461.  How to Trap WM_COMMAND and commandMode Messages
  11462.  
  11463.  /* Handle menu events and accelerator keys identically.
  11464.     self refers to the ProjWindow that receives the message.
  11465.  */
  11466.  Def  WM_COMMAND(self, wParam, lParam)
  11467.  {
  11468.    select
  11469.      case wParam == PW_FILE_OPEN        /* ^O or menu */
  11470.        fileOpenAs(self);
  11471.      endCase
  11472.      case wParam == PW_FILE_NEW         /* ^N or menu */
  11473.        fileNew(self);
  11474.      endCase
  11475.                  .
  11476.                  .  <other cases omitted... >
  11477.                  .
  11478.      case wParam == PW_HELP             /* F1 or menu */
  11479.        help(self);
  11480.      endCase
  11481.      case wParam == PW_COMMAND_MODE     /* slash or Esc */
  11482.        commandMode(self);
  11483.      endCase
  11484.    endSelect;
  11485.  }
  11486.  /* Enter "command mode" in response to a slash key
  11487.     accelerator.  This simulates Lotus 1-2-3 style
  11488.     commands by sending an Alt key sysCommand message. */
  11489.  
  11490.  Def  commandMode(self)
  11491.  
  11492.  {
  11493.    WM_SYSCOMMAND(self, 0xF100, 0L);
  11494.  }
  11495.  
  11496.  
  11497.  Figure 10:
  11498.  
  11499.  Def command(self, wParam, lParam)
  11500.  {
  11501.    perform(self, actions[wParam]);
  11502.  }
  11503.  
  11504.  
  11505.  Dialog Boxes
  11506.  
  11507.  Dialog boxes represent a more advanced challenge. In PC-Project I
  11508.  needed several types of dialog boxes to allow data to be edited. For
  11509.  example, when the user clicks the mouse button in the project window, I want
  11510.  to bring up a dialog box that lets the user edit the selected activity's
  11511.  name, description, starting date, and so on. This is complicated slightly by
  11512.  the fact that different types of activities have different data and thus
  11513.  require different dialog boxes. For example, tasks have time and cost,
  11514.  whereas milestones do not.
  11515.  
  11516.  Because all the activity classes--that is, Milestones, Tasks, and
  11517.  PERTTasks (tasks with an estimated time)--are logically similar, they
  11518.  descend from a single class called Activity. The Activity class is known as
  11519.  a formal class, since we will never create objects of that class, only
  11520.  objects of the descendant classes.
  11521.  
  11522.  By making good use of inheritance, we can minimize the amount of code that
  11523.  needs to be written. A general editInfo method can be written for the
  11524.  Activity class that will be inherited by Milestone, Task, and PERTTask. When
  11525.  the user clicks on an activity in the project window, we determine which
  11526.  activity is selected and then send it an editInfo message.
  11527.  
  11528.  However, the editInfo method must be able to run the appropriate dialog box
  11529.  for each type of activity. How do we know which type of dialog box to run?
  11530.  We simply send a message to the activity. Descendants of the Activity class
  11531.  that use the editInfo method should define a method called dialogClass that
  11532.  returns the type of dialog to use when editing. The dialogClass method is
  11533.  considered part of the public protocol for activities.
  11534.  
  11535.  The source code to trap the mouse click and edit an activity in Figure 11
  11536.  shows the WM_LBUTTONDOWN method for the ProjWindow class.
  11537.  
  11538.  In the same way that the Milestone class descends from the formal class
  11539.  Activity, the MStoneDialog class descends from the formal class ActivDialog,
  11540.  which in turn descends from the Actor class Dialog. In this section I will
  11541.  use higher-level Actor methods that hide some of the details of programming
  11542.  dialog boxes.
  11543.  
  11544.  Dialogs are created by sending the dialog objects a runModal message. The
  11545.  runModal message requires as arguments the resource ID (a constant) and a
  11546.  parent window. The layout of the dialog and its fields, known as edit
  11547.  controls, are defined in the resource script file. Before a dialog actually
  11548.  runs, Actor sends an initDialog message. We can trap this message to load
  11549.  the edit controls with initial values. Since edit controls are objects, they
  11550.  manage the details of handling keyboard input, tabbing, and so on.
  11551.  
  11552.  By making use of inheritance I have the ActivDialog class initialize the
  11553.  edit controls that it knows about and the MStoneDialog class initialize the
  11554.  additional edit controls that it adds. To initialize an edit control with a
  11555.  value, you send a setItemText message to the dialog and specify as arguments
  11556.  a constant indicating the edit control and the value to be used. For
  11557.  example, the message
  11558.  
  11559.  setItemText(self, NAME,
  11560.       getName(activity));
  11561.  
  11562.  will load the name of the activity being edited into the edit control named
  11563.  NAME.
  11564.  
  11565.  I wrote several access methods, such as getName, getEarlyStart, and
  11566.  setValues that provide a safe way to access an activity's private data.
  11567.  Therefore, if I change the representation of a Milestone, I don't need to
  11568.  change code in other classes. This helps maintain a logical division in the
  11569.  program. The dialog box is responsible only for editing and does not need to
  11570.  be concerned with whether changes to the data require a recalculation of the
  11571.  critical path; that is the responsibility of the setValues method of the
  11572.  activity.
  11573.  
  11574.  The command method defined for the ActivDialog is used to trap user events
  11575.  related to the dialog. In this case, there are only two events that we're
  11576.  interested in--clicking on OK or clicking on Cancel--any other
  11577.  event is ignored. If the user clicks on OK, an update message is sent to the
  11578.  dialog box. Although the command method is defined in the ActivDialog class,
  11579.  it is inherited by the MStoneDialog, TaskDialog, and PERTDialog classes,
  11580.  which define their own update method. The update method sends several other
  11581.  messages, including setValues, which informs the activity that was being
  11582.  edited of its new values so that it can take appropriate action. The code
  11583.  required for the dialog box classes is shown in Figure 12.
  11584.  
  11585.  The other dialog classes, like TaskDialog or PERTDialog, work in a similar
  11586.  fashion, but they require less code because of inheritance. They merely have
  11587.  to initialize any additional edit controls in the initDialog message and
  11588.  define their own setValues method; everything else is inherited.
  11589.  
  11590.  Figure 11:
  11591.  
  11592.  /* Respond to a left button mouse click message. The lParam is the point in
  11593.  the window where clicked. Convert the window location into a "logical"
  11594.  display point and then send a displayLookup message to the project. This
  11595.  message will return the activity at the location, which is considered to be
  11596.  logically true, or logically false if there is no activity at that location.
  11597.  If there is an activity, edit it; otherwise just beep. The variable self
  11598.  refers to the ProjWindow that receives the message. The variables dPoint and
  11599.  activity are local.
  11600.  */
  11601.  Def  WM_LBUTTONDOWN(self, wParam, lParam | dPoint, activity)
  11602.  {
  11603.    dPoint := windowToDisplay(self, lParam);    /* convert */
  11604.    activity :=displayLookup(project, dPoint); /* find it */
  11605.    if activity             /* logically true if found */
  11606.      editInfo(activity);   /* user clicked on an activity */
  11607.    else                    /* false if nothing found */
  11608.      beep();               /* user clicked on dead space */
  11609.    endif;
  11610.  }
  11611.  
  11612.  The editInfo Method for Class Activity and Descendants
  11613.  
  11614.  /* Display and edit an activity's information. Descendants that use this
  11615.  method should have a dialogClass() method that returns the type of dialog to
  11616.  be used.
  11617.  
  11618.     self refers to the activity that received the message.
  11619.     dlg and retValue are local variables.
  11620.     ThePort is the main window used as the dialog' s parent.
  11621.  */
  11622.  Def  editInfo(self | dlg, retValue)
  11623.  {
  11624.    showWaitCurs();                 /* show an hour glass */
  11625.    dlg := new(dialogClass(self));  /* we know what kind */
  11626.    setEditItem(dlg, self);         /* what to edit? self! */
  11627.    retValue := run(dlg, ThePort);  /* run the dialog */
  11628.    showOldCurs();                  /* change cursor back */
  11629.    ^retValue;                      /* return the run value */
  11630.  }
  11631.  
  11632.  The dialogClass Method for Class Milestone
  11633.  
  11634.  /* Return the appropriate Actor dialog class for editing.
  11635.  */
  11636.  Def  dialogClass(self)
  11637.  {
  11638.    ^MStoneDialog;
  11639.  }
  11640.  
  11641.  Figure 12:
  11642.  
  11643.  /* This is a formal class to define behavior common to the various activity
  11644.  dialog boxes in PC-Project. Descendants should define the res() method to
  11645.  return the resource ID to be used, initDialog() to initialize additional
  11646.  fields, and update() to update values in the activity.
  11647.  
  11648.     ActivDialog descends from class Dialog and inherits all
  11649.     of its methods and variables.
  11650.  */
  11651.  
  11652.  /* Set the object being edited. */
  11653.  Def  setEditItem(self, anEditItem)
  11654.  {
  11655.    activity := anEditItem;
  11656.  }
  11657.  
  11658.  /* Run the dialog with the appropriate resource. */
  11659.  Def  run(self, parent | retValue)
  11660.  {
  11661.    ^runModal(self, res(self), parent);
  11662.  }
  11663.  
  11664.  /* Initialize all of the fields in the dialog.
  11665.     Descendants may wish to initialize additional fields or override
  11666.     this method.
  11667.  */
  11668.  Def  initDialog(self, wParam, lp)
  11669.  {
  11670.    setText(self, makeCaption(activity));
  11671.  
  11672.    setItemText(self, NAME, getName(activity));
  11673.    setItemText(self, DESC, getDesc(activity));
  11674.    setItemText(self, UES, getUserEarlyStart(activity));
  11675.    setItemText(self, ULF, getUserLateFinish(activity));
  11676.    setItemText(self, ES, asString(getEarlyStart(activity)));
  11677.    setItemText(self, EF, asString(getEarlyFinish(activity)));
  11678.    setItemText(self, LS, asString(getLateStart(activity)));
  11679.    setItemText(self, LF, asString(getLateFinish(activity)));
  11680.    setItemText(self, SLACK, asString(getSlack(activity)));
  11681.  }
  11682.  
  11683.  /* Handle the Ok and Cancel buttons. If Ok was clicked, then update
  11684.     the activity. This command method is used by descendants. They
  11685.     will define their own update method. */
  11686.  Def command(self, wParam, lParam)
  11687.  {
  11688.    select
  11689.      case wParam == IDOK
  11690.         update(self);
  11691.         end(self, IDOK);
  11692.      endCase
  11693.      case wParam == IDCANCEL
  11694.         end(self, IDCANCEL);
  11695.      endCase
  11696.      default
  11697.        ^1;        /* ignore it */
  11698.    endSelect;
  11699.    ^0;
  11700.  }
  11701.  
  11702.  MStoneDialog class [in black bar]
  11703.  
  11704.  /* The MstoneDialog class descends from class ActivDialog
  11705.     and inherits all of its methods and variables.
  11706.  */
  11707.  
  11708.  /* Return the resource ID used with this dialog box. */
  11709.  Def  res(self)
  11710.  {
  11711.    ^MSTONE_BOX;
  11712.  }
  11713.  
  11714.  
  11715.  /* Initialize additional fields in the dialog.
  11716.     Uses the ancestor's initDialog first.
  11717.  */
  11718.  Def  initDialog(self, wParam, lParam)
  11719.  {
  11720.    initDialog(self:ActivDialog, wParam, lParam);
  11721.    setItemText(self, INPUT, getInputNames(activity));
  11722.    setItemText(self, OUTPUT, getOutputNames(activity));
  11723.  }
  11724.  
  11725.  
  11726.  /* Update the activity after Ok was pressed.
  11727.     Inform the network if the name changes,
  11728.     check the connections and set the values.
  11729.  */
  11730.  Def  update(self)
  11731.  {
  11732.    setName(activity, getItemText(self, NAME));
  11733.    addNode(getNetwork(activity), activity);
  11734.    checkConnection(activity,
  11735.            getItemText(self, INPUT),
  11736.            getItemText(self, OUTPUT));
  11737.    setValues(self);
  11738.  }
  11739.  
  11740.  
  11741.  /* Set the values of the activity.  checkDate displays
  11742.     an error message if the date is illegal.
  11743.  */
  11744.  Def  setValues(self | ues, ulf)
  11745.  {
  11746.    ues := checkDate(getItemText(self, UES));
  11747.    ulf := checkDate(getItemText(self, ULF));
  11748.    setValues(activity, getItemText(self, NAME),
  11749.        getItemText(self, DESC), ues, ulf);
  11750.  }
  11751.  
  11752.  
  11753.  Worth the Effort
  11754.  
  11755.  I hope that this "under-the-hood" discussion of PC-Project has helped
  11756.  illustrate the concepts of object-oriented programming and how Windows
  11757.  works. Sometimes it seems like a lot of work to program for Windows, but the
  11758.  end result, a program with a graphical user interface, consistent commands,
  11759.  and device independence makes it worthwhile.
  11760.  
  11761.  Although it is difficult to make use of polymorphism and inheritance in
  11762.  procedural languages, I encourage you to use object-oriented design
  11763.  techniques no matter what language you are using. By designing objects that
  11764.  encompass both data and their operations and by clearly separating public
  11765.  protocol and private implementation, you will be able to write more
  11766.  understandable and maintainable code.
  11767.  
  11768.  
  11769.  
  11770.  Sidebar
  11771.  
  11772.  Actor, designed and created by The Whitewater Group, is an object-oriented
  11773.  language that allows you to create standalone Microsoft Windows
  11774.  applications. Since Actor is an interactive environment that runs under
  11775.  Windows, you can type statements in the workspace window and get immediate
  11776.  feedback. Windows, menus, and dialogs can be created and modified directly
  11777.  in the development environment. Actor has a source-level debugger and an
  11778.  inspector that lets you debug programs interactively.
  11779.  
  11780.  Code is written in a browser, a special editor that lets you create new
  11781.  classes and compiles methods as you write them. The compiler translates
  11782.  Actor statements into a low-level format used at run time. The browser also
  11783.  shows the hierarchy of classes in the system. Figure A shows a screen shot
  11784.  of a browser in the Actor development environment.
  11785.  
  11786.  Actor is a pure object-oriented language. That means that everything in the
  11787.  system is an object and all operations are performed by sending messages to
  11788.  objects. Even an expression like 5 * X is a message to X to multiply itself
  11789.  by 5. In hybrid languages such as C++, some things are objects that respond
  11790.  to messages and others are not.
  11791.  
  11792.  Actor's syntax is a combination of Pascal and C. For example, the := symbol
  11793.  is used for assignment, whereas curly braces, { }, denote a block. Comments
  11794.  appear within C-style delimiters, /* and */. Actor includes an if/else
  11795.  statement, a case/select statement, while loops, and so on.
  11796.  
  11797.  Method definitions begin with the keyword Def, followed by the name of the
  11798.  method and parameters within parentheses. The first parameter in a method
  11799.  definition is the receiver of the message and is always called self. The
  11800.  vertical bar, |, is used to separate arguments from temporary local
  11801.  variables. No type definitions are necessary, since Actor determines the
  11802.  class of an object at run time. The sample method definition shown in Figure
  11803.  B has two arguments, resource and parent, and one local variable, retValue.
  11804.  
  11805.  The caret, ^, is used to return a value. If no value is explicitly returned,
  11806.  the receiver of the message, self, is returned.
  11807.  
  11808.  This very brief view of Actor indicates that it has a relatively standard
  11809.  procedural language syntax. It differs from procedural languages in the way
  11810.  it is programmed. Programming in Actor can be summarized as the process of
  11811.  creating objects and sending messages to the objects. Messages provide the
  11812.  objects with the information necessary to execute a method. In a sense, the
  11813.  message is matched to a method already defined within the object. Actor is
  11814.  dedicated to the goal of creating high-level applications that hide complex
  11815.  underlying issues.
  11816.  
  11817.  For more information about Actor, contact:
  11818.  
  11819.  The Whitewater Group
  11820.  
  11821.  Technology Innovation Center
  11822.  
  11823.  906 University Place
  11824.  
  11825.  Evanston, IL 60201
  11826.  
  11827.  (312) 491-2370
  11828.  
  11829.  
  11830.  MDI: An Emerging Standard for Manipulating Document Windows
  11831.  
  11832.   Kevin P. Welch
  11833.  
  11834.  The Multiple Document Interface (referred to herein as MDI) is a user
  11835.  interface style developed for Microsoft(R) Windows and OS/2 Presentation
  11836.  Manager (referred to herein as PM) that supports the viewing of multiple
  11837.  child windows within a main application. Each of these smaller child windows
  11838.  can be used to display different sets of data or multiple views of the same
  11839.  set of data.
  11840.  
  11841.  This article describes MDI, focusing on the user interface as well as
  11842.  programming aspects of the standard. In the process, it describes a Windows
  11843.  library (MDI.LIB), which will let you easily incorporate the MDI interface
  11844.  into your own applications. The use of this library will then be
  11845.  demonstrated within the context of a simple application (COLORS.EXE).
  11846.  Finally, the MDI standard will be contrasted and compared to the IBM(R)
  11847.  Systems Application Architecture (SAA) Common User Access (CUA) guidelines
  11848.  for user interfaces.
  11849.  
  11850.  Background & Motivation
  11851.  
  11852.  Windows and PM developers have long been fascinated with applications that
  11853.  contain windows within windows. This interest stems from both the natural
  11854.  capabilities of the host environments and the obvious original influence of
  11855.  the Apple(R) Macintosh(R). MDI can therefore be considered an outgrowth of
  11856.  programmers' interest and experience as they have attempted to create a
  11857.  Macintosh-like environment inside Windows. This work, after considerable
  11858.  refinement in the Windows environment, is now being applied to OS/2 PM.
  11859.  
  11860.  To a degree, the development of MDI satisfied some creative instincts
  11861.  expressed by developers while also providing Macintosh-like functionality
  11862.  for IBM-compatible personal computers. In addition, its specification is
  11863.  facilitating the development of an entirely new class of interoperable
  11864.  applications that sport similar user interfaces on the Macintosh, Windows,
  11865.  and OS/2 PM.
  11866.  
  11867.  From this beginning, the MDI specification was refined and extended
  11868.  (primarily by Microsoft) in a determined effort to make it reasonably CUA
  11869.  conformant. The result of this effort was the formal definition of MDI in
  11870.  the Microsoft Windows Software Development Kit (SDK) followed by its
  11871.  implementation in Microsoft Excel--the first major application to use
  11872.  the new specification.
  11873.  
  11874.  Following the lead of Microsoft Excel, many software developers have tried
  11875.  using multiple child windows in their applications, but unfortunately only a
  11876.  few have succeeded in fully implementing the original specification. This
  11877.  failure is due in part to the fact that MDI is reasonably difficult to
  11878.  implement correctly since it requires a good low-level understanding of the
  11879.  underlying environment. Furthermore, implementing MDI in Windows involves a
  11880.  number of subtle tricks, which in the best circumstances might be considered
  11881.  poor programming practice.
  11882.  
  11883.  Despite its difficulties, in recent months the acceptance of MDI has been
  11884.  further solidified with its incorporation into the new (although in my
  11885.  opinion not very well designed) OS/2 file system. This coupled with the also
  11886.  new but more consistent PM programming model should further enhance its
  11887.  appeal to both developers and publishers alike.
  11888.  
  11889.  Definition & Specification
  11890.  
  11891.  From the start, it must be clearly understood that MDI is a user interface
  11892.  style; that is, it is not a set of absolute rules but a collection of a few
  11893.  underdefined guidelines. Although many software developers have implemented
  11894.  MDI within the general style guidelines, few have implemented it in exactly
  11895.  the same way.
  11896.  
  11897.  The first thing to understand about MDI is its main, or top-level, desktop
  11898.  window. This window is almost always resizable, with a title bar equipped
  11899.  with a standard system menu and minimize/maximize icons. In most
  11900.  circumstances, the title bar, or caption, of the main window contains only
  11901.  the name of the application (more on this later). As with most windows, the
  11902.  standard system menu is provided with the following options and
  11903.  accelerators:
  11904.  
  11905.  Restore    Alt-F5
  11906.  Move    Alt-F7
  11907.  Size    Alt-F8
  11908.  Minimize    Alt-F9
  11909.  Maximize    Alt-F10
  11910.  Close    Alt-F4
  11911.  
  11912.  The main window is also used to display each of the application menus
  11913.  belonging to its associated child windows. The application menus vary
  11914.  according to the type of document the active child window contains. For
  11915.  example, when the user moves from a child window that contains a chart
  11916.  document to one that contains a spreadsheet document, the main application
  11917.  menu changes to reflect the capabilities of the active child window. Some
  11918.  menu items can remain active regardless of which child window is selected
  11919.  (for example, various file or formatting commands that have equivalent
  11920.  meaning in different contexts).
  11921.  
  11922.  In addition, the main menu provides a Window management pull-down menu.
  11923.  Commands on the pull-down menu are applicable regardless of the child window
  11924.  selected, allowing the user to manage the main window and all its children.
  11925.  The first part of the pull-down menu has commands that allow the user to
  11926.  manage the size, position, and visibility of each of the child windows. The
  11927.  next part has a list of all the currently visible (including iconic) child
  11928.  windows.
  11929.  
  11930.  The New command lets the user create a new view into the currently selected
  11931.  document. That is, the user creates a new child window that contains the
  11932.  currently active document. Although this might not always be appropriate, it
  11933.  is useful in situations in which the user wishes to view a different portion
  11934.  of the same document. It could be used, for example, to support multiple
  11935.  enlargements of a drawing in a paint program: one window could contain the
  11936.  entire image and another a detailed view.
  11937.  
  11938.  Since screen "real estate" is quite limited, most MDI implementations
  11939.  incorporate some mechanism to arrange the various child windows. Here,
  11940.  following the New command, are two commands that help manage the visual
  11941.  arrangement of the child windows.
  11942.  
  11943.  The Tile command "tiles" each of the active child windows inside the parent
  11944.  client area. Although many effective tiling algorithms can be devised, most
  11945.  assign some sort of priority to the currently active child window and place
  11946.  it in the largest space available. Note that in most implementations the
  11947.  Tile command is a one-time event--any subsequent movement of the child
  11948.  windows will destroy the tiling.
  11949.  
  11950.  The Tile Always command is an extension of the Tile command as it forces all
  11951.  of the child windows to remain continuously tiled. When the size of one
  11952.  child window is adjusted, the relative sizes of the other children are
  11953.  changed to compensate. Any change to the parent window or to one of the
  11954.  children automatically causes a tiling to occur around it (much as it did
  11955.  with Windows Version 1.03). Although this technique has not been used in any
  11956.  major Windows or PM applications, it has many merits that warrant serious
  11957.  consideration.
  11958.  
  11959.  Frequently users end up with many documents open simultaneously, resulting
  11960.  in a cluttered desktop. Following the tiling commands are therefore two
  11961.  commands that enable the user to hide or show one or more child windows. The
  11962.  Hide command lets the user hide the currently active child window. One
  11963.  popular variation on this theme is to let the user simultaneously hide one
  11964.  or more child windows using a dialog box that contains a multiple selection
  11965.  list box. This makes it possible for the user to clear a portion of the
  11966.  desktop in one fluid motion.
  11967.  
  11968.  When windows are hidden, they remain active but cannot be accessed. By using
  11969.  the Unhide command, the user can select one or more windows (from a list of
  11970.  all hidden windows) and make them visible again. The windows can be restored
  11971.  to their original size and location or, if tiling is active, merged into the
  11972.  desktop.
  11973.  
  11974.  The last group of items on the Window pull-down menu is a list of all
  11975.  currently active child windows. The windows are listed by title, with each
  11976.  title preceded by a digit that serves as a short mnemonic. This facilitates
  11977.  quick and consistent keyboard access to each child window regardless of the
  11978.  current title. If a currently active child window exists, it is indicated by
  11979.  a check mark beside its title.
  11980.  
  11981.  In some applications, certain commands may only be applicable to the main
  11982.  window. When this is the case, the main window may be listed at the
  11983.  beginning of the window list. This allows the user to access the commands
  11984.  that are supported by the main window quite easily. Applications that do not
  11985.  need this feature can omit the main window from the window list.
  11986.  
  11987.  Child Windows
  11988.  
  11989.  The next thing to understand about MDI--after its main desktop
  11990.  window--is its associated child windows. Like the desktop, each child
  11991.  window is resizable and contains a title bar. The title bar normally has the
  11992.  name of the document being edited. If a single document is being viewed by
  11993.  more than one child window, a number is appended after the document name;
  11994.  for example,
  11995.  
  11996.  CHART.XLC:1
  11997.  CHART.XLC:2
  11998.  CHART.XLC:3
  11999.  
  12000.  Only one child window can be active at a time, and it is distinguished from
  12001.  the others by a change in the color or pattern of the title bar (usually
  12002.  with the same mechanisms used to differentiate the main desktop from other
  12003.  top-level windows). Note that the main desktop window remains active when
  12004.  one of the child windows is enabled. To a certain extent, this appears to be
  12005.  a visual contradiction since the input focus seems to be simultaneously
  12006.  shared between two windows, to say nothing of the programmatic hoops you
  12007.  have to jump through to accomplish this sleight of hand.
  12008.  
  12009.  The active MDI child window also contains a control or system menu box.
  12010.  Although similar to the one maintained by the parent window, it is activated
  12011.  by the Alt-Minus key combination (I'm not completely sure of the rationale
  12012.  behind this). The commands on the child system menu are identical to those
  12013.  on the main window, except that the Alt key is replaced by the Ctrl key. The
  12014.  end result is a system menu that contains the following options and
  12015.  accelerators:
  12016.  
  12017.  Restore    Ctrl-F5
  12018.  Move    Ctrl-F7
  12019.  Size    Ctrl-F8
  12020.  Minimize    Ctrl-F9
  12021.  Maximize    Ctrl-F10
  12022.  Close    Ctrl-F4
  12023.  
  12024.  It is left up to the application to disable or gray any of these commands
  12025.  that are inappropriate. In most implementations, only the Minimize command
  12026.  is disabled.
  12027.  
  12028.  The Move and Size commands (accessed by Ctrl-F7 and Ctrl-F8, respectively)
  12029.  allow the location and size of the child window to be controlled. These
  12030.  functions mirror the ones available on the desktop but are restricted to
  12031.  keep the child window inside the parent. Like most operations, the movement
  12032.  and resizing of the child windows can also be accomplished using the mouse.
  12033.  
  12034.  An interesting item to note is how the child window frame is handled when
  12035.  moved. Although the mouse is clipped to the client area of the desktop, in
  12036.  Windows the frame can extend outside the parent window boundary. In PM
  12037.  implementations of MDI, the frame is clipped by the system to the client
  12038.  area of the desktop.
  12039.  
  12040.  The Minimize command (Ctrl-F9), seldom implemented in Windows, reduces the
  12041.  child window to an icon inside the MDI desktop. The resulting icon can then
  12042.  be selected, moved around the desktop to a new location, and restored to its
  12043.  original size and location. As is the case with all visible child windows in
  12044.  the MDI desktop, the icon can be hidden or selected using the Window
  12045.  pull-down menu. Note that throughout this process the icon must remain
  12046.  inside the client area of the desktop window and cannot be moved elsewhere
  12047.  on the display. Although this is relatively easy to accomplish in OS/2 PM,
  12048.  it adds a whole new level of complexity to a Windows implementation of MDI
  12049.  (and so is seldom implemented). In PM, however, it is considerably easier to
  12050.  implement this feature, and I expect that more applications will take
  12051.  advantage of it when implementing MDI. Refer to the WITHIN sample
  12052.  application in the MS(R) OS/2 Software Development Kit Version 1.1 (OS/2
  12053.  SDK) if you're curious.
  12054.  
  12055.  The Maximize command (Ctrl-F10) causes the child window to be enlarged to
  12056.  fill the entire client area of the desktop window. As a shortcut, you can
  12057.  use the mouse and click inside the maximize icon or double-click anywhere in
  12058.  the title bar.
  12059.  
  12060.  Since the client area of the child window fills the main window, you can
  12061.  consider that the title bar of the child "slides under" the menu bar of the
  12062.  desktop window. When this happens, two other changes occur. The first
  12063.  regards the main window caption. Originally, it contains only the
  12064.  application name. But when a child window is maximized, the title bar of the
  12065.  desktop is changed to include the name of the currently active document,
  12066.  much as is done in normal, non-MDI applications. The second, and perhaps
  12067.  even more complicated change, involves the movement of the child window's
  12068.  system menu to the beginning of the application menu. This allows continued
  12069.  access to the child system menu, letting you close or restore the window to
  12070.  its original size.
  12071.  
  12072.  If you think things are complicated enough, consider what happens when a new
  12073.  MDI child is created or an existing one is selected while another is
  12074.  maximized. The MDI specification dictates that when a different child window
  12075.  is selected or a new one created, it automatically assumes the
  12076.  characteristics of the previously selected window. This implies that if you
  12077.  create a new child window while in a maximized state, the new window will
  12078.  also be displayed in a maximized state. Similarly, when you close a
  12079.  maximized child window, the MDI desktop automatically selects the next
  12080.  available child window and maximizes it for you--whether you wanted it
  12081.  to or not.
  12082.  
  12083.  The Restore command (Ctrl-F5), as you might expect, causes the maximized
  12084.  child window (or minimized if implemented) to be restored to its original
  12085.  size and location among the other windows, much as it does with top-level
  12086.  system menus.
  12087.  
  12088.  Finally, the Close command (Ctrl-F4) destroys the currently selected child
  12089.  window. In situations in which the child window is one of several views into
  12090.  a common document, the title bars of the remaining windows are automatically
  12091.  renumbered to reflect the change. If the child window being closed is the
  12092.  last one accessing a document, a dialog box is normally displayed to confirm
  12093.  any required save operations. As is the case with all system menus,
  12094.  double-clicking the mouse inside the system menu box is a shortcut for
  12095.  choosing the Close command.
  12096.  
  12097.  Another item to note about all these commands is that they apply only to the
  12098.  child window that is currently active. This means only that the window has a
  12099.  system menu and minimize/maximize menu boxes. Unfortunately, the original
  12100.  MDI specification does not make this terribly clear. The end result is that
  12101.  each of the child windows is responsible for changing its visible attributes
  12102.  when it receives and loses the input focus.
  12103.  
  12104.  If that isn't enough, the MDI specification also calls for a number of
  12105.  keyboard accelerators to move between the various child windows:
  12106.  
  12107.  F6    select next active document subdivision, clockwise
  12108.  
  12109.  Shift-F6    select next active document subdivision, counterclockwise
  12110.  
  12111.  Ctrl-F6    select next active document, from front to back
  12112.  
  12113.  Shift-Ctrl-F6    select next active document, from back to front
  12114.  
  12115.  Despite the fact that the keyboard accelerators are not listed on the child
  12116.  system menu, they represent the only mechanism for moving between child
  12117.  windows without a mouse. It is left up to users to remember (assuming they
  12118.  read their manuals) what they are and how they work. Furthermore, the task
  12119.  of implementing these accelerators is yet another activity that must be
  12120.  managed by the already overburdened MDI desktop window.
  12121.  
  12122.  Design Issues
  12123.  
  12124.  Before I get into the actual programming issues involved with implementing
  12125.  MDI, it seems appropriate to discuss the design issues that are bound to
  12126.  come up when working with the specification. Perhaps foremost is the
  12127.  additional complexity associated with using MDI. If you don't have the idea
  12128.  by now, it takes a great deal of time to implement MDI and get it to work
  12129.  correctly. From a design standpoint, MDI requires that each child window be
  12130.  object-oriented in nature (maintaining its own instance data) yet be able to
  12131.  access shared data that is held in common when multiple views are in effect.
  12132.  In addition, the standard has some serious performance implications since it
  12133.  introduces more support code and yet another level of hierarchy into the
  12134.  system.
  12135.  
  12136.  It is also natural to compare multiple instances of tightly coupled
  12137.  applications to the MDI alternative. On the positive side, a group of
  12138.  independent applications are often easier to design, implement, and test,
  12139.  especially when the environment takes care of the data-handling issues for
  12140.  you. This approach works especially well when using non-Windows-aware
  12141.  applications or those whose hold on the environment is tenuous in the best
  12142.  of circumstances.
  12143.  
  12144.  On the negative side are the resulting cluttered display, lack of
  12145.  interoperable consistency with the Macintosh, and difficulties of
  12146.  well-integrated interprocess communication between separate applications.
  12147.  Furthermore, since many applications require the use of multiple views into
  12148.  the same document, MDI is something you will probably have to think
  12149.  seriously about.
  12150.  
  12151.  Another troublesome design area with MDI is the way that it treats menus.
  12152.  Although menus are relatively simple to structure when each child window is
  12153.  homogeneous in nature and shares the same or similar capabilities, the
  12154.  design can be difficult when child windows are very heterogeneous or involve
  12155.  compound documents.
  12156.  
  12157.  Menu design is further complicated when the desktop is empty or all the
  12158.  child windows are hidden. Most of the normal menu options will be disabled
  12159.  and of no interest to the user. Many applications respond to this situation
  12160.  by severely pruning their menus, adding additional menu-handling complexity.
  12161.  
  12162.  One of the most serious drawbacks of the MDI standard is the problem that
  12163.  results when the desktop window is resized. Although you can't move a child
  12164.  window completely outside the client area, it is possible to have it end up
  12165.  there if you change the size of the MDI parent. The end result is a window
  12166.  (perhaps even an active one) that is completely invisible. Despite the fact
  12167.  that you can't point to the invisible window with a mouse, you can use the
  12168.  child window accelerators to get to it--but you still can't see it.
  12169.  
  12170.  An interesting visual phenomenon can be created if you use the Ctrl-Minus
  12171.  key combination while the active child window is outside the client area. As
  12172.  you would expect, the child's system menu appears but the child window
  12173.  remains hidden. The result is that the child's system menu appears
  12174.  unconnected to the desktop, magically making itself visible with no apparent
  12175.  connection to anything else. Interesting, maybe, but certainly somewhat
  12176.  confusing for users.
  12177.  
  12178.  MDI API
  12179.  
  12180.  By now you may be wondering whether MDI is something you want to take
  12181.  on--especially since it doesn't really do anything except manage a
  12182.  collection of related child windows. But there is a good reason to use
  12183.  it--the definition and implementation of an Application Program
  12184.  Interface (API) that manages the MDI. The end result of the API is a small
  12185.  library of object modules (approximately 16Kb in total size) that performs
  12186.  all the work of integrating MDI into your application for you. And best of
  12187.  all, it's no extra charge with the price of your MSJ subscription.
  12188.  
  12189.  In order to accomplish the task of integrating the API into an application I
  12190.  enlisted the help of friend, long-time associate, and Windows
  12191.  guru--Geoffrey Nicholls. Together we came up with an API that lets you
  12192.  write complex MDI applications as if they were standalone, independent
  12193.  applications.
  12194.  
  12195.  We recognized from the start that developing such an API would be quite
  12196.  dirty (doing things we would never do in conventional Windows programming)
  12197.  and that we would really have to try to keep it small and simple. We also
  12198.  realized that we would not be able to implement the entire specification,
  12199.  only some of the more important facets--the rest we would leave to you.
  12200.  Finally, we wanted to make it as object-oriented as possible. After several
  12201.  false starts and rewrites we ended up accomplishing our goals but in so
  12202.  doing perhaps used property lists and window offsets to excess.
  12203.  
  12204.  The MDI API we came up with in a sense represents an analog of the existing
  12205.  Windows API. From previous experience, we knew that to a large degree the
  12206.  operating characteristics of a window are defined by the default window
  12207.  message processing function. The MDI API attempts to change this foundation
  12208.  and give each window a new and different set of characteristics. The net
  12209.  result is a small number of routines with familiar parameter lists that can
  12210.  be used together to give your application MDI characteristics.
  12211.  
  12212.  MdiMainCreateWindow()    Main window creation function
  12213.  MdiMainDefWindowProc()    Main MDI window default window function
  12214.  MdiChildCreateWindow()    MDI child window creation function
  12215.  MdiChildDefWindowProc()    MDI child window default window function
  12216.  MdiGetMessage()    MDI application message retrieval function
  12217.  MdiTranslateAccelerators()    MDI application translate accelerator function
  12218.  MdiGetMenu()    MDI menu retrieval function
  12219.  MdiSetAccel()    MDI set document window accelerator table function
  12220.  
  12221.  Message Flow and Process Sequencing
  12222.  
  12223.  In the next two sections we will examine the inner workings of the MDI API.
  12224.  We first describe the general message flow and processing sequence used by
  12225.  the API. Then we describe each of the top-level functions and explain some
  12226.  of the subtle ways in which they work. As you read these sections, refer to
  12227.  the MDI source code listings accompanying this article. The code is
  12228.  reasonably well documented, so you should be able to understand it if you
  12229.  have a good background in Windows programming.
  12230.  
  12231.  Now, perhaps the most efficient way to learn about the MDI API is to study
  12232.  the MDI message flow diagram carefully. It tracks the path of each message
  12233.  received by an application that uses the API, focusing on those that are of
  12234.  particular interest.
  12235.  
  12236.  The first thing to notice is the rather normal message retrieval,
  12237.  translation, and dispatch loop at the top of the diagram. This occurs much
  12238.  as it would in any other Windows application, the only difference being in a
  12239.  specific check for menu-related messages (performed inside the MdiGetMessage
  12240.  function). When such messages are encountered, they are immediately
  12241.  dispatched to a window function in order to activate the various system and
  12242.  application menus correctly.
  12243.  
  12244.  After the message-handling loop, each message is dispatched to an
  12245.  appropriate window function. As far as MDI is concerned, there are only two
  12246.  types of windows present--a desktop or parent window and child or
  12247.  document windows. On the left side of the diagram, the flow of events for
  12248.  the desktop window is listed; on the right side, a similar flow for each of
  12249.  the document windows is listed.
  12250.  
  12251.  Tracing down the left, or desktop side, each message is processed by the
  12252.  main application window function, then passed on to the default MDI main
  12253.  window function. The remainder of events listed below occur inside this
  12254.  default function, finally ending in most messages being sent on to the
  12255.  standard DefWindowProc.
  12256.  
  12257.  As you can see from the diagram, the default MDI main window function is
  12258.  primarily interested in activation, initialization, and command-related
  12259.  messages. All other messages are sent on without modification to the
  12260.  DefWindowProc. Of those intercepted, some result in a particular action
  12261.  being performed (like the activation of a particular child window); others
  12262.  are processed and sent directly to an appropriate child
  12263.  window--bypassing the default window function.
  12264.  
  12265.  On the right, or document side of the diagram is the sequence of events that
  12266.  occur when messages are received by the default MDI child window function.
  12267.  As is the case with the left side, the flow of events listed occur inside
  12268.  this default function, ending with most of the messages being sent on to the
  12269.  standard DefWindowProc.
  12270.  
  12271.  Like the default desktop window function, the child window function is
  12272.  primarily interested in activation, initialization, and command-related
  12273.  messages. Of particular importance are the various system commands. Some are
  12274.  handled directly and not passed to the system. In certain cases, such as
  12275.  those that involve activation of a menu or a new document window, the
  12276.  message is sent directly to the desktop.
  12277.  
  12278.  In a normal Windows application, the activation of a child window occurs
  12279.  with little fanfare, but in an MDI implementation a number of important
  12280.  steps must be performed. The first is changing the title bar color. Although
  12281.  Windows allows only one window to have the input focus, when a child window
  12282.  is selected it seems that both the desktop and the child are simultaneously
  12283.  active. This is done by manually sending a WM_NCPAINT message (with
  12284.  appropriate parameters) to the DefWindowProc of the window being activated.
  12285.  
  12286.  Furthermore, under the interface specification, only the currently active
  12287.  document window contains an MDI system menu and maximize/minimize icons.
  12288.  Because of this, the second step is to change the style of the window to
  12289.  include these new attributes and then force the system to display the
  12290.  changes.
  12291.  
  12292.  The next major task to perform when a document window is activated involves
  12293.  replacing the desktop menu. Each child window is associated with its own
  12294.  menu. When it is activated, the current desktop menu is replaced and the new
  12295.  one inserted, retaining all the attributes that it previously had. Finally,
  12296.  just before the input focus is transferred to the document, the Window
  12297.  pull-down menu has to be updated to reflect the current status of the
  12298.  desktop.
  12299.  
  12300.  Like Figure 12, Figures 13 and 14 list the sequence of events that
  12301.  transpires either when a document window is maximized or a different
  12302.  document window is selected while in maximized state. Of these two
  12303.  sequences, the only somewhat technical task is the automatic insertion of
  12304.  the MDI system menu at the beginning of the application menu. This involves
  12305.  retrieving the MDI menu icon from Windows with the following call:
  12306.  
  12307.  hBitmap = LoadBitmap(
  12308.        NULL,
  12309.        MAKEINTRESOURCE(
  12310.        OBM_CLOSE) );
  12311.  
  12312.  The resulting bitmap contains both the standard system menu icon and the MDI
  12313.  one. After retrieving the bitmap dimensions (via a GetObject call), you can
  12314.  extract the MDI system menu bitmap for use when updating the application
  12315.  menu. This entire sequence of events (never before publicly documented) is
  12316.  performed by the MdiCreateChildSysBitmap function, should you have the
  12317.  inclination to see how it is actually accomplished.
  12318.  
  12319.  Top-Level Functions
  12320.  
  12321.  Now that I've discussed the message flow and process sequencing of MDI, I
  12322.  will focus on the top-level function calls provided by the API.
  12323.  Programmatically speaking, if you understand these functions, you will be
  12324.  able to use the MDI interface in your own applications without a great deal
  12325.  of difficulty.
  12326.  
  12327.  The first of these calls is MdiMainCreateWindow, which is responsible for
  12328.  the creation of the main desktop window and all the associated MDI property
  12329.  lists required to make the interface work. The actual data structures used
  12330.  by the API are maintained with property lists attached to the desktop and
  12331.  document windows. A property list is an attempt to give each window access
  12332.  to some sort of instance data (to borrow an object-oriented programming
  12333.  term). This is accomplished by associating a window handle with a named
  12334.  block of memory. Using the interface provided by Windows, any window can
  12335.  set, enumerate, retrieve, and destroy properties. Although there is no
  12336.  predefined limit to the number of properties that a window can have, the
  12337.  property list itself, like other window-related data, is actually allocated
  12338.  in the local heap of the user library. The MdiMainCreateWindow function also
  12339.  attaches the Window pull-down menu to the main application menu, making the
  12340.  MDI interface almost transparent to the desktop.
  12341.  
  12342.  The second API call is MdiMainDefWindowProc. As described in the MDI message
  12343.  flow diagram, this function is responsible for the default processing of all
  12344.  desktop-related messages. More specifically, it is interested in messages
  12345.  that involve the activation and deactivation of the desktop, menu-related
  12346.  messages, and messages relating to the visibility and sizing of the
  12347.  associated document windows. Implemented as a large switch statement, it
  12348.  passes most of the messages on to the DefWindowProc.
  12349.  
  12350.  The next API call is the MdiChildCreateWindow function. This function,
  12351.  identical in many ways to the standard Windows CreateWindow call, creates a
  12352.  new child window inside the desktop. Behind the scenes it sets up the
  12353.  related property lists, keeps track of the window menu and accelerator
  12354.  table, and activates the child window correctly, depending on the current
  12355.  state of the desktop.
  12356.  
  12357.  As with the desktop window, each document window is associated with a
  12358.  default MDI message-processing function, MdiChildDefWindowProc. This
  12359.  function is responsible for the default handling of all document window
  12360.  related messages, especially those that involve system menu choices and
  12361.  window creation, activation, and destruction. Those messages not handled are
  12362.  either sent directly to the desktop window or are passed on to the
  12363.  DefWindowProc.
  12364.  
  12365.  After the default child window function is a replacement for the standard
  12366.  Windows message function, MdiGetMessage. This function is responsible for
  12367.  retrieving all of the application messages from the system queue. It also
  12368.  checks for keyboard menu access and activates the correct menu when cursor
  12369.  keys are used while a pull-down menu is visible.
  12370.  
  12371.  Following this is the MdiTranslateAccelerators function, which is
  12372.  responsible for translating each message according to the currently active
  12373.  accelerator table. Though most Windows applications have only one
  12374.  accelerator table, MDI applications can have several--one for the main
  12375.  desktop and one for each type of document window. This function
  12376.  automatically checks the state of the application and uses the appropriate
  12377.  accelerator table.
  12378.  
  12379.  Finally, there are two utility functions contained in the
  12380.  MDI API--MdiGetMenu and MdiSetAccel--that are implemented as
  12381.  macros. These two functions are required since most applications need to
  12382.  define an accelerator table and access the current menu. The current menu
  12383.  handle is retrieved by the MdiGetMenu macro which returns it to the document
  12384.  window. The MdiSetAccel macro attaches an accelerator table to the property
  12385.  list of the document window. The accelerator table can then be used
  12386.  automatically when messages are translated and dispatched throughout the
  12387.  application.
  12388.  
  12389.  Taken together, these eight functions represent the entire MDI API. Although
  12390.  you cannot use these functions with impunity, they should work well even in
  12391.  the most demanding applications. If carefully used, they hide most of the
  12392.  subtleties of MDI and let you focus on solving customer problems, not
  12393.  implementing yet another scheme for managing child windows. The only really
  12394.  nasty side effect is the installation of a system keyboard message hook.
  12395.  This hook intercepts cursor movement keystrokes while manipulating menus.
  12396.  Without this message hook it would be very difficult to implement a truly
  12397.  authentic MDI keyboard user interface.
  12398.  
  12399.  Building MDI.LIB
  12400.  
  12401.  In order to build the MDI API library, you will have to create these files:
  12402.  
  12403.  MDI    Library make file
  12404.  MDI.H    Library header file
  12405.  MDI1.C    Main MDI API functions
  12406.  MDI2.C    Activation and switching of document windows
  12407.  MDI3.C    Handling of menus and keyboard user interface
  12408.  
  12409.  In addition to these source files, you will need the Microsoft Windows
  12410.  Version 2.1 SDK and the Microsoft C Optimizing Compiler Version 5.1.
  12411.  
  12412.  The library MAKE file (MDI) will compile each of the modules in the medium
  12413.  model and combine them into an object library using the LIB utility provided
  12414.  with the C compiler. The resulting library is then ready for use without
  12415.  modification by any medium model Windows application. If you wish, you can
  12416.  change the make file compilation flags and create equivalent small, compact,
  12417.  or large versions of the same library.
  12418.  
  12419.  Using the MDI API
  12420.  
  12421.  I will use the program COLORS.EXE to show how the MDI API is used in the
  12422.  context of a simple application. I chose this program since it will clearly
  12423.  demonstrate the simple and straightforward use of the MDI API. In many ways,
  12424.  COLORS can be considered a collection of three different, yet related
  12425.  programs. For one, although the three parts of COLORS share the same window
  12426.  procedure, they act as if they were three separate applications. Using the
  12427.  MDI API they are brought together into one desktop.
  12428.  
  12429.  With the COLORS desktop, you can create a number of red, green, and blue
  12430.  colored document windows. The colored windows are created by using the
  12431.  New... option under the File pull-down menu. Each window is successively
  12432.  numbered and, via an associated pull-down menu, can be modified to display
  12433.  different intensities of color. Of the three types of document windows, the
  12434.  blue one is unique in that it is also associated with an accelerator table.
  12435.  By using keys 0, 1, 2, 5, and 7 you can change the intensity of the blue
  12436.  background color to 0 percent, 100 percent, 25 percent, 50 percent, and 75
  12437.  percent respectively. See Figure 16 for an example.
  12438.  
  12439.  When several documents are present on the desktop, you can move from window
  12440.  to window using one of three mechanisms--selecting a window with the
  12441.  mouse, moving to another window using the keyboard user interface, or
  12442.  pulling down the Window menu and manually selecting a different window.
  12443.  Additionally, using the Window pull-down menu, you can hide the currently
  12444.  active document window or possibly redisplay hidden ones in a traditional
  12445.  MDI fashion.
  12446.  
  12447.  You can build COLORS from the source code listed in Figure 17, which
  12448.  includes the following files:
  12449.  
  12450.  COLORS
  12451.  COLORS.DEF
  12452.  COLORS.RC
  12453.  COLORS.H
  12454.  COLORS.C
  12455.  
  12456.  Each reference to the MDI API is clearly identified and highlighted in
  12457.  Figure 17. The first reference to notice is in the application MAKE File.
  12458.  Here, COLORS is dependent on both MDI.H and MDI.LIB. In addition, the MDI
  12459.  library is referenced in the linkage command line, which allows COLORS to
  12460.  use any of the public MDI API routines we previously defined.
  12461.  
  12462.  The next MDI reference of interest is contained in COLORS.DEF, which is
  12463.  where both the MdiMsgHook and MdiDlgUnhide functions are exported. They both
  12464.  must be exported since they represent movable entry points used by the
  12465.  system. Failure to do so will cause serious problems.
  12466.  
  12467.  The third reference to the MDI API is in COLORS.RC. Note the inclusion of
  12468.  the MDI.H header file and the definition of a number of MDI-related
  12469.  resources at the end of the file. The first of these resources, the MDI
  12470.  window menu, is used as a template for the Window pull-down menu. The MDI
  12471.  API creates a duplicate of this menu, attaching a list of the currently
  12472.  visible document windows at the end.
  12473.  
  12474.  The next resource is the MDI child window accelerator table. This table,
  12475.  which is automatically loaded by the API, is used to implement the document
  12476.  window keyboard user interface. Last in the resource file is the template
  12477.  for the MDI Unhide dialog box. This dialog box is displayed when the
  12478.  Unhide... command is selected from the Window pull-down menu. With this
  12479.  dialog box you can select a hidden document window and have it redisplayed
  12480.  on the desktop. You can also change the style and characteristics of the
  12481.  dialog box to suite your application, although you should be careful not to
  12482.  alter the name and identifiers used.
  12483.  
  12484.  Following the resource file is COLORS.C, which contains all of the C source
  12485.  code for the COLORS application and is structured much like any other
  12486.  windows program. COLORS.C (like COLORS.RC) also references MDI.H. In
  12487.  addition to defining all MDI related identifiers, MDI.H needs to be included
  12488.  since it defines function prototypes for each member of the MDI API.
  12489.  
  12490.  The first MDI-related task COLORS performs is the creation of the main
  12491.  desktop window using the MdiMainCreateWindow function inside MainInit. This
  12492.  call creates an empty window that contains the default desk-top menu. Not
  12493.  long after MdiMainCreateWindow is a call to ColorCreate, a utility function
  12494.  that creates a new document window using the MdiChildCreateWindow function
  12495.  and associates it with an appropriate accelerator table. In this case, the
  12496.  default action is to create a single red document window.
  12497.  
  12498.  Once the desktop has been created and initialized, the application
  12499.  retrieves and processes all related messages. Like most Windows
  12500.  applications, this is accomplished with a simple GetMessage loop followed by
  12501.  the translation and dispatch of each message retrieved. In this case,
  12502.  however, MdiGetMessage and MdiTranslateAccelerators are used in place of the
  12503.  normal Windows functions.
  12504.  
  12505.  The next reference to the MDI API occurs in the MainWndProc of COLORS.
  12506.  Each message that is dispatched by the message-processing loop is sent
  12507.  directly to the responsible window function. The MainWndProc handles all the
  12508.  messages that relate to the desktop window. In addition, since the desktop
  12509.  window is the only one that contains a menu, it also receives all
  12510.  menu-related messages.
  12511.  
  12512.  The desktop message processing function traps only the file-related commands
  12513.  and passes the rest of the messages to the MdiMainDefWindowProc for
  12514.  additional handling. The MdiMainDefWindowProc in turn processes the commands
  12515.  in which it is interested (redirecting some to the appropriate document
  12516.  window function) and passes the rest on to the system via the DefWindowProc.
  12517.  
  12518.  Throughout this process, the main window message processing function can
  12519.  receive menu commands belonging to any one of the child windows. Because of
  12520.  this capability, it is recommended that each document window menu share a
  12521.  common set of commands that are applicable at the desktop level. In COLORS,
  12522.  these commands are all those listed under the File pull-down menu.
  12523.  
  12524.  Note that it is only necessary to conceptually separate the desktop and
  12525.  document menus, not each of the associated document menus. This is because
  12526.  menu commands not intercepted by the desktop are only destined for the
  12527.  currently active document window, not for those that are inactive.
  12528.  
  12529.  The last references to the MDI API occur in the ColorWndProc
  12530.  message-processing function. This function, shared among each of the colored
  12531.  child windows, responds to the document menu commands and paints the window
  12532.  background using the default color at the selected intensity level.
  12533.  Throughout ColorWndProc, MdiGetMenu is used in place of GetMenu. This is
  12534.  because the desktop window contains the menu for the document window and
  12535.  isn't always the immediate parent [else GetMenu(GetParent(hWnd)) would be a
  12536.  suitable alternative].
  12537.  
  12538.  Like the desktop window function, ColorWndProc passes most of the messages
  12539.  on to the default MDI message-processing function, in this case
  12540.  MdiChildDefWindowProc. This function in turn processes a subset of the
  12541.  messages and passes the balance on to the DefWindowProc. In certain
  12542.  situations, messages are redirected to the desktop window and not sent
  12543.  directly to the system.
  12544.  
  12545.  When you build COLORS, experiment with it and see how the internal functions
  12546.  respond in a variety of situations. Try and hide all the document windows or
  12547.  create new ones while one is in a maximized state. In particular try out the
  12548.  keyboard user interface, moving from document to document without the aid of
  12549.  a mouse.
  12550.  
  12551.  In a while you will begin to appreciate how much is going on in the
  12552.  background to make the interface work consistently. Yet despite the visual
  12553.  sophistication, there is the increased overhead required by the API. If you
  12554.  switch rapidly between different document windows, then the additional
  12555.  overhead will be readily apparent. Although in part due to the relatively
  12556.  simpleminded message-handling approach of COLORS (which passes everything on
  12557.  to the default window function), to a large degree it can be attributed to
  12558.  the MDI API itself.
  12559.  
  12560.  Nevertheless, keep in mind that the MDI API implemented here was designed
  12561.  for clarity and readability, not for size and performance. Our internal
  12562.  working version of the API (on which the published library was based)
  12563.  implemented the full MDI specification considerably more efficiently than
  12564.  this one does (including Window New, Tiling, and the ability to minimize
  12565.  document windows). The central structure, however, remains the
  12566.  same--with a little tuning and enhancement, the base API presented here
  12567.  is capable of supporting world-class MDI applications with unparalleled
  12568.  ease. Coupled with a little rethinking of your current data-handling
  12569.  techniques, you will be able to adapt many of your existing Windows
  12570.  applications to the MDI user interface easily. And, perhaps best of all,
  12571.  with the MDI API you can accomplish this with few changes to your source
  12572.  code.
  12573.  
  12574.  MDI and SAA
  12575.  
  12576.  In addition to the interoperability benefits of MDI, one of the most
  12577.  significant forces behind its acceptance in the development community is
  12578.  IBM's SAA. Although a comprehensive overview of SAA is outside the scope of
  12579.  this article, we will briefly describe it to show how the SAA specification
  12580.  influences MDI.
  12581.  
  12582.  SAA is a set of selected software interfaces, conventions, and protocols
  12583.  that serve as a common framework for application development, portability,
  12584.  and use across three major computing platforms--the IBM System/370,
  12585.  System/3X, and the personal computer.
  12586.  
  12587.  A significant part of SAA is the CUA specification. This standard defines,
  12588.  in a lengthy set of rules and guidelines, what SAA-compliant user interfaces
  12589.  should look like and how they are to be used. The end result is a 300+ page
  12590.  document (available from your local IBM representative or branch office)
  12591.  that describes in laborious detail the SAA human/machine interface.
  12592.  
  12593.  Why are SAA and CUA important? Regardless of what you think about them, the
  12594.  compelling fact of the matter is that many large corporations are attempting
  12595.  to settle on a common user interface that spans a variety of hardware
  12596.  platforms. This drive is in part motivated by the hope that users will be
  12597.  able to migrate easily from machine to machine without the customary
  12598.  learning curve associated with the transition. Corporations are starting to
  12599.  require vendors to provide SAA, CUA-compliant software. Microsoft has been
  12600.  actively trying to capitalize on this by making Windows SAA, CUA-compliant
  12601.  (hence all the unusual keyboard accelerators).
  12602.  
  12603.  MDI, being an integral part of the Microsoft Windows strategy, fits into
  12604.  this overall standard. The net effect--and this is why MDI is important
  12605.  for you as a software developer--is that if you use the MDI interface
  12606.  (as opposed to some other scheme) in your application, your potential users
  12607.  will already be familiar with the interface and you could potentially sell
  12608.  more software. At the very least, you should take a close look at the IBM
  12609.  SAA, CUA specification and give it careful consideration. Personally, I
  12610.  have a hard time living with the constraints CUA puts on me as a developer,
  12611.  but I am willing to live with them if I can put my programs in front of a
  12612.  larger customer base.
  12613.  
  12614.  I hope this discussion has given you ideas and insights that will help you
  12615.  in your own development. MDI just might be the answer to some technical
  12616.  problem you are struggling with. As you consider MDI, realize that to a
  12617.  large extent it has evolved from the need for an organized way of handling
  12618.  multiple documents within a single desktop. This evolution has been at best
  12619.  troublesome and is still somewhat at odds with the underlying environment.
  12620.  Perhaps in the future something like the MDI API might be included in the
  12621.  Windows or OS/2 Presentation Manager API, saving both you and me a great
  12622.  deal of effort. Until that time, you have access to a little more
  12623.  information than you did before.
  12624.  
  12625.  COLORS - MAKE File
  12626.  
  12627.  # compilation flags
  12628.  CFLAGS=-AM -c -Gsw -Osal -W2 -Zp
  12629.  
  12630.  # COLORS
  12631.  colors.res: colors.rc colors.ico colors.h mdi.h
  12632.     rc -r colors.rc
  12633.  
  12634.  colors.obj: colors.c colors.h mdi.h
  12635.     cl $(CFLAGS) colors.c
  12636.  
  12637.  colors.exe: colors.obj colors.def mdi.lib
  12638.     link4 colors,colors/ALIGN:16,colors,mdi+mlibw+mlibcew/NOE,colors
  12639.     rc colors.res
  12640.  
  12641.  colors.exe: colors.res
  12642.     rc colors.res
  12643.  
  12644.  
  12645.  
  12646.  COLORS.DEF - DEF File
  12647.  
  12648.  NAME                    COLORS
  12649.  DESCRIPTION             'Multiple Document Interface'
  12650.  STUB                    'WINSTUB.EXE'
  12651.  
  12652.  CODE                    MOVEABLE DISCARDABLE PURE LOADONCALL SHARED
  12653.  DATA                    MOVEABLE MULTIPLE
  12654.  
  12655.  HEAPSIZE                2048
  12656.  STACKSIZE               2048
  12657.  
  12658.  EXPORTS
  12659.     MainWndProc          @1
  12660.     ColorWndProc         @2
  12661.     MainDlgNew           @3
  12662.     MainDlgAbout         @4
  12663.     MdiMsgHook           @5
  12664.     MdiDlgUnhide         @6
  12665.  
  12666.  COLORS.RC - Resource File
  12667.  
  12668.  /* COLORS.RC - Resources for COLORS program */
  12669.  
  12670.  /* COLORS section of file */
  12671.  
  12672.  #include <windows.h>
  12673.  #include "colors.h"
  12674.  #include "mdi.h"
  12675.  
  12676.  MainIcon ICON  colors.ico
  12677.  
  12678.  MainMenu MENU
  12679.  BEGIN
  12680.     POPUP "&File"
  12681.     BEGIN
  12682.        MENUITEM "&New...",         IDM_NEW
  12683.        MENUITEM "&Open...",        IDM_OPEN, GRAYED
  12684.        MENUITEM "&Save",           IDM_SAVE, GRAYED
  12685.        MENUITEM "Save &As...",     IDM_SAVEAS, GRAYED
  12686.        MENUITEM "&Close",          IDM_CLOSE, GRAYED
  12687.        MENUITEM SEPARATOR
  12688.        MENUITEM "&Exit",           IDM_EXIT
  12689.        MENUITEM "A&bout...",       IDM_ABOUT
  12690.     END
  12691.  END
  12692.  
  12693.  RedMenu MENU
  12694.  BEGIN
  12695.     POPUP "&File"
  12696.     BEGIN
  12697.        MENUITEM "&New...",         IDM_NEW
  12698.        MENUITEM "&Open...",        IDM_OPEN, GRAYED
  12699.        MENUITEM "&Save",           IDM_SAVE, GRAYED
  12700.        MENUITEM "Save &As...",     IDM_SAVEAS, GRAYED
  12701.        MENUITEM "&Close",          IDM_CLOSE
  12702.        MENUITEM SEPARATOR
  12703.        MENUITEM "&Exit",           IDM_EXIT
  12704.        MENUITEM "A&bout...",       IDM_ABOUT
  12705.     END
  12706.     POPUP "&Red"
  12707.     BEGIN
  12708.        MENUITEM "&0 %",            IDM_0
  12709.        MENUITEM "&25 %",           IDM_25
  12710.        MENUITEM "&50 %",           IDM_50
  12711.        MENUITEM "&75 %",           IDM_75
  12712.        MENUITEM "&100 %",          IDM_100, CHECKED
  12713.     END
  12714.  END
  12715.  
  12716.  GreenMenu MENU
  12717.  BEGIN
  12718.     POPUP "&File"
  12719.     BEGIN
  12720.        MENUITEM "&New...",         IDM_NEW
  12721.        MENUITEM "&Open...",        IDM_OPEN, GRAYED
  12722.        MENUITEM "&Save",           IDM_SAVE, GRAYED
  12723.        MENUITEM "Save &As...",     IDM_SAVEAS, GRAYED
  12724.        MENUITEM "&Close",          IDM_CLOSE
  12725.        MENUITEM SEPARATOR
  12726.        MENUITEM "&Exit",           IDM_EXIT
  12727.        MENUITEM "A&bout...",       IDM_ABOUT
  12728.     END
  12729.     POPUP "&Green"
  12730.     BEGIN
  12731.        MENUITEM "&0 %",            IDM_0
  12732.        MENUITEM "&25 %",           IDM_25
  12733.        MENUITEM "&50 %",           IDM_50
  12734.        MENUITEM "&75 %",           IDM_75
  12735.        MENUITEM "&100 %",          IDM_100, CHECKED
  12736.     END
  12737.  END
  12738.  
  12739.  BlueMenu MENU
  12740.  BEGIN
  12741.     POPUP "&File"
  12742.     BEGIN
  12743.        MENUITEM "&New...",         IDM_NEW
  12744.        MENUITEM "&Open...",        IDM_OPEN, GRAYED
  12745.        MENUITEM "&Save",           IDM_SAVE, GRAYED
  12746.        MENUITEM "Save &As...",     IDM_SAVEAS, GRAYED
  12747.        MENUITEM "&Close",          IDM_CLOSE
  12748.        MENUITEM SEPARATOR
  12749.        MENUITEM "&Exit",           IDM_EXIT
  12750.        MENUITEM "A&bout...",       IDM_ABOUT
  12751.     END
  12752.     POPUP "&Blue"
  12753.     BEGIN
  12754.        MENUITEM "&0 %\t  0",       IDM_0
  12755.        MENUITEM "&25 %\t  2",      IDM_25
  12756.        MENUITEM "&50 %\t  5",      IDM_50
  12757.        MENUITEM "&75 %\t  7",      IDM_75
  12758.        MENUITEM "&100 %\t  1",     IDM_100, CHECKED
  12759.     END
  12760.  END
  12761.  
  12762.  BlueAccel   ACCELERATORS
  12763.  BEGIN
  12764.     "0",        IDM_0
  12765.     "2",        IDM_25
  12766.     "5",        IDM_50
  12767.     "7",        IDM_75
  12768.     "1",        IDM_100
  12769.  END
  12770.  
  12771.  STRINGTABLE
  12772.  BEGIN
  12773.     IDS_TITLE,              "Multiple Document Interface"
  12774.     IDS_MAINCLASS,          "MdiMainClass"
  12775.     IDS_COLORCLASS,         "MdiChildClass"
  12776.  END
  12777.  
  12778.  MainNew  DIALOG   50, 50, 144, 60
  12779.  STYLE WS_DLGFRAME | WS_POPUP
  12780.  BEGIN
  12781.     GROUPBOX "New"                              -1,   4,  4, 100, 52
  12782.     RADIOBUTTON "&Red",                 DLGNEW_RED,   8, 16,  88, 10
  12783.     RADIOBUTTON "&Green",             DLGNEW_GREEN,   8, 28,  88, 10
  12784.     RADIOBUTTON "&Blue",               DLGNEW_BLUE,   8, 40,  88, 10
  12785.     DEFPUSHBUTTON "&OK"                       IDOK, 108,  8,  32, 14
  12786.     PUSHBUTTON "&Cancel"                  IDCANCEL, 108, 28,  32, 14
  12787.  END
  12788.  
  12789.  MainAbout   DIALOG   22, 17, 156, 100
  12790.  STYLE WS_DLGFRAME | WS_POPUP
  12791.  BEGIN
  12792.      CTEXT "Multiple Document Interface",        -1,  0,  8,152,  8
  12793.      CTEXT "By Geoffrey D. Nicholls",            -1,  0, 20,152,  8
  12794.      CTEXT "and Kevin P. Welch",                 -1,  0, 28,152,  8
  12795.      CTEXT "(C) Copyright 1988",                 -1,  0, 40,152,  8
  12796.      CTEXT "Eikon Systems, Inc.",                -1,  0, 48,152,  8
  12797.      CTEXT "Version 1.00  1-Nov-88",             -1,  0, 60,152,  8
  12798.      DEFPUSHBUTTON "Ok"              IDOK, 60, 80, 32, 14, WS_GROUP
  12799.  END
  12800.  
  12801.  
  12802.  /* MDI section of file */
  12803.  
  12804.  MdiMenu MENU
  12805.  BEGIN
  12806.     MENUITEM "&New",               IDM_NEWWINDOW, GRAYED
  12807.     MENUITEM SEPARATOR
  12808.     MENUITEM "&Tile",              IDM_ARRANGE, GRAYED
  12809.     MENUITEM "Tile &Always",       IDM_ARRANGEALL, GRAYED
  12810.     MENUITEM SEPARATOR
  12811.     MENUITEM "&Hide",              IDM_HIDE, GRAYED
  12812.     MENUITEM "&Unhide...",         IDM_UNHIDE
  12813.  END
  12814.  
  12815.  MdiChildAccel  ACCELERATORS
  12816.  BEGIN
  12817.     VK_F4,      IDM_CLOSE,        VIRTKEY, NOINVERT, CONTROL
  12818.     VK_F5,      IDM_RESTORE,      VIRTKEY, NOINVERT, CONTROL
  12819.     VK_F6,      IDM_NEXTWINDOW,   VIRTKEY, NOINVERT, CONTROL
  12820.     VK_F6,      IDM_PREVWINDOW,   VIRTKEY, NOINVERT, CONTROL, SHIFT
  12821.     VK_F7,      IDM_MOVE,         VIRTKEY, NOINVERT, CONTROL
  12822.     VK_F8,      IDM_SIZE,         VIRTKEY, NOINVERT, CONTROL
  12823.     VK_F10,     IDM_MAXIMIZE,     VIRTKEY, NOINVERT, CONTROL
  12824.  END
  12825.  
  12826.  MdiUnhide   DIALOG 50, 50, 132, 68
  12827.  STYLE WS_DLGFRAME | WS_POPUP
  12828.  BEGIN
  12829.     LTEXT "&Unhide",                            -1,   4,  4,  88, 10
  12830.     LISTBOX                  DLGUNHIDE_LB, 4, 16, 88, 48, WS_TABSTOP
  12831.     DEFPUSHBUTTON "&OK"                       IDOK,  96,  8,  32, 14
  12832.     PUSHBUTTON "&Cancel"                  IDCANCEL,  96, 28,  32, 14
  12833.  END
  12834.  
  12835.  COLORS.H - Header File
  12836.  
  12837.  / * COLORS.H - Include for COLORS program */
  12838.  
  12839.  /* Resource file constants */
  12840.  
  12841.  /* Strings */
  12842.  #define  IDS_TITLE                1
  12843.  #define    IDS_MAINCLASS          2
  12844.  #define    IDS_COLORCLASS         3
  12845.  
  12846.  /* Debugging menu choice */
  12847.  #define    IDM_DEBUG              0x100
  12848.  
  12849.  /* File Menu Choices */
  12850.  #define    IDM_NEW                0x101
  12851.  #define    IDM_OPEN               0x102
  12852.  #define    IDM_SAVE               0x103
  12853.  #define    IDM_SAVEAS             0x104
  12854.  #define    IDM_PRINT              0x105
  12855.  #define    IDM_ABOUT              0x106
  12856.  #define    IDM_EXIT               0x107
  12857.  
  12858.  /* Color Menu Choices */
  12859.  #define    IDM_0                  0x108
  12860.  #define    IDM_25                 0x109
  12861.  #define    IDM_50                 0x10a
  12862.  #define    IDM_75                 0x10b
  12863.  #define    IDM_100                0x10c
  12864.  
  12865.  /* New dialog box */
  12866.  #define    DLGNEW_RED             0x100
  12867.  #define    DLGNEW_GREEN           0x101
  12868.  #define    DLGNEW_BLUE            0x102
  12869.  
  12870.  
  12871.  /* Window extra constants */
  12872.  
  12873.  #define    WE_COLOR               0
  12874.  #define    WE_SHADE               2
  12875.  #define    WE_EXTRA               4
  12876.  
  12877.  #define    COLOR_RED              0
  12878.  #define    COLOR_GREEN            1
  12879.  #define    COLOR_BLUE             2
  12880.  
  12881.  
  12882.  /* Function prototypes */
  12883.  
  12884.  int PASCAL WinMain( HANDLE, HANDLE, LPSTR, int );
  12885.  
  12886.  HWND              MainInit( HANDLE, HANDLE, int );
  12887.  long FAR PASCAL   MainWndProc( HWND, unsigned, WORD, LONG );
  12888.  BOOL              ColorInit( HANDLE );
  12889.  HWND              ColorCreate( HWND, int );
  12890.  long FAR PASCAL   ColorWndProc( HWND, unsigned, WORD, LONG );
  12891.  int FAR PASCAL    MainDlgNew( HWND, unsigned, WORD, LONG );
  12892.  int FAR PASCAL    MainDlgAbout( HWND, unsigned, WORD, LONG );
  12893.  
  12894.  COLORS.C - Source File for COLORS.EXE
  12895.  
  12896.  /* COLORS.C - Colorful MDI Children */
  12897.  
  12898.  #include <string.h>
  12899.  #include <stdio.h>
  12900.  #include <windows.h>
  12901.  
  12902.  #include "colors.h"
  12903.  #include "mdi.h"
  12904.  
  12905.  /* Static variables */
  12906.  
  12907.  /* Text for client area */
  12908.  static char    *szShadings[5] = {"0 %", "25 %", "50 %",
  12909.                                   "75 %", "100 %"};
  12910.  
  12911.  /* Titles of documents */
  12912.  static char    *szTitles[3] = { "Red", "Green", "Blue" };
  12913.  
  12914.  /* Count of each document (for titles) */
  12915.  static int     wCounts[3] = { 0, 0, 0 };
  12916.  
  12917.  /* Color & Shading table */
  12918.  static DWORD   rgbColors[3][5] = {
  12919.                    {  /* RED */
  12920.                    RGB(255,255,255),    /*   0 % */
  12921.                    RGB(255,192,192),    /*  25 % */
  12922.                    RGB(255,128,128),    /*  50 % */
  12923.                    RGB(255,64,64),      /*  75 % */
  12924.                    RGB(255,0,0)         /* 100 % */
  12925.                    },
  12926.                    {  /* GREEN */
  12927.                    RGB(255,255,255),    /*   0 % */
  12928.                    RGB(192,255,192),    /*  25 % */
  12929.                    RGB(128,255,128),    /*  50 % */
  12930.                    RGB(64,255,64),      /*  75 % */
  12931.                    RGB(0,255,0)         /* 100 % */
  12932.                    },
  12933.                    {  /* BLUE */
  12934.                    RGB(255,255,255),    /*   0 % */
  12935.                    RGB(192,192,255),    /*  25 % */
  12936.                    RGB(128,128,255),    /*  50 % */
  12937.                    RGB(64,64,255),      /*  75 % */
  12938.                    RGB(0,0,255)         /* 100 % */
  12939.                    } };
  12940.  
  12941.  /*  * First routine called by windows.  Calls the initialization routine
  12942.   * and contains the message loop. */
  12943.  
  12944.  int PASCAL WinMain(
  12945.     HANDLE      hInst,
  12946.     HANDLE      hPrevInst,
  12947.     LPSTR       lpszCmdLine,
  12948.     int         nCmdShow )
  12949.  {
  12950.     HWND        hwndColors;       /* Handle to our MDI desktop */
  12951.     MSG         msg;              /* Current message */
  12952.  
  12953.     /* Initialize things needed for this application */
  12954.     hwndColors = MainInit( hPrevInst, hInst, nCmdShow );
  12955.     if ( !hwndColors )
  12956.     {
  12957.        /* Failure to initialize */
  12958.        return NULL;
  12959.     }
  12960.  
  12961.     /* Process messages */
  12962.     while ( MdiGetMessage( hwndColors, &msg, NULL, NULL, NULL ) )
  12963.     {
  12964.        /* Normal message processing */
  12965.        if ( !MdiTranslateAccelerators( hwndColors, &msg ) )
  12966.        {
  12967.           TranslateMessage( &msg );
  12968.           DispatchMessage( &msg );
  12969.        }
  12970.     }
  12971.  
  12972.     /* Done */
  12973.     return msg.wParam;
  12974.  }
  12975.  
  12976.  /* * First, initialize the MDI desktop and the color document windows.
  12977.  Second, create the MDI desktop and then create one red document. */
  12978.  
  12979.  HWND MainInit(
  12980.     HANDLE      hPrevInst,
  12981.     HANDLE      hInst,
  12982.     int         nCmdShow )
  12983.  {
  12984.     char        szTitle[80];      /* Title of our MDI desktop */
  12985.     char        szClass[80];      /* Class name of our MDI desktop */
  12986.     HWND        hwndColors;       /* Handle to our MDI desktop */
  12987.     WNDCLASS    WndClass;         /* Class structure */
  12988.  
  12989.     /* Window classes */
  12990.     if ( !hPrevInst )
  12991.     {
  12992.        /* Main window */
  12993.        LoadString( hInst, IDS_MAINCLASS, szClass, sizeof( szClass ) );
  12994.  
  12995.        /* Prepare registration */
  12996.        memset( &WndClass, 0, sizeof( WndClass ) );
  12997.        WndClass.style          = CS_HREDRAW | CS_VREDRAW;
  12998.        WndClass.lpfnWndProc    = MainWndProc;
  12999.        WndClass.hInstance      = hInst;
  13000.        WndClass.hIcon          = LoadIcon( hInst, "MainIcon" );
  13001.        WndClass.hCursor        = LoadCursor( NULL, IDC_ARROW );
  13002.        WndClass.hbrBackground  = COLOR_APPWORKSPACE + 1;
  13003.        WndClass.lpszMenuName   = "MainMenu";
  13004.        WndClass.lpszClassName  = szClass;
  13005.  
  13006.        /* Register main class */
  13007.        if ( !RegisterClass( &WndClass ) )
  13008.           return NULL;
  13009.  
  13010.        /* Allow each of the MDI children to do its own initialize */
  13011.        if ( !ColorInit(hInst) )
  13012.           return NULL;
  13013.     }
  13014.  
  13015.     /* Create our main overlapped window */
  13016.     LoadString( hInst, IDS_TITLE, szTitle, sizeof( szTitle ) );
  13017.     hwndColors = MdiMainCreateWindow( szClass,
  13018.        szTitle,
  13019.        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
  13020.        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  13021.        NULL,
  13022.        NULL,
  13023.        hInst,
  13024.        NULL );
  13025.  
  13026.     /* Did we create successfully? */
  13027.     if ( !hwndColors )
  13028.        return NULL;
  13029.  
  13030.     /* Give us one red child to begin with */
  13031.     if ( !ColorCreate( hwndColors, COLOR_RED ) )
  13032.     {
  13033.        DestroyWindow( hwndColors );
  13034.        return NULL;
  13035.     }
  13036.  
  13037.     /* Ready */
  13038.     ShowWindow( hwndColors, nCmdShow );
  13039.     UpdateWindow( hwndColors );
  13040.  
  13041.     /* Done */
  13042.     return hwndColors;
  13043.  }
  13044.  
  13045.  /* Handle messages for our MDI desktop.  This includes any WM_COMMAND
  13046.  messages that are received when NO document is visible on the desktop.*/
  13047.  
  13048.  long FAR PASCAL MainWndProc(
  13049.     HWND        hwndColors,
  13050.     unsigned    message,
  13051.     WORD        wParam,
  13052.     LONG        lParam )
  13053.  {
  13054.     FARPROC     lpProc;           /* Procedure instance for dialogs */
  13055.     HANDLE      hInst;            /* Current instance handle */
  13056.  
  13057.     switch ( message )
  13058.     {
  13059.     case WM_COMMAND:
  13060.        hInst = GetWindowWord( hwndColors, GWW_HINSTANCE );
  13061.        switch( wParam )
  13062.        {
  13063.        case IDM_NEW:
  13064.           /* New dialog box */
  13065.           lpProc = MakeProcInstance( MainDlgNew, hInst );
  13066.           switch( DialogBox( hInst, "MainNew", hwndColors, lpProc ) )
  13067.           {
  13068.           case DLGNEW_RED:
  13069.              ColorCreate( hwndColors, COLOR_RED );
  13070.              break;
  13071.  
  13072.           case DLGNEW_GREEN:
  13073.              ColorCreate( hwndColors, COLOR_GREEN );
  13074.              break;
  13075.  
  13076.           case DLGNEW_BLUE:
  13077.              ColorCreate( hwndColors, COLOR_BLUE );
  13078.              break;
  13079.           }
  13080.           FreeProcInstance( lpProc );
  13081.           break;
  13082.  
  13083.        case IDM_OPEN:
  13084.           break;
  13085.  
  13086.        case IDM_ABOUT:
  13087.           /* About dialog box */
  13088.           lpProc = MakeProcInstance( MainDlgAbout, hInst );
  13089.           DialogBox( hInst, "MainAbout", hwndColors, lpProc );
  13090.           FreeProcInstance( lpProc );
  13091.           break;
  13092.  
  13093.        case IDM_EXIT:
  13094.           /* Tell application to shut down */
  13095.           PostMessage( hwndColors, WM_SYSCOMMAND, SC_CLOSE, 0L );
  13096.           break;
  13097.        }
  13098.        break;
  13099.  
  13100.     case WM_DESTROY:
  13101.        PostQuitMessage( 0 );
  13102.        break;
  13103.     }
  13104.     return MdiMainDefWindowProc(hwndColors, message, wParam, lParam);
  13105.  }
  13106.  
  13107.  /* Register the document class. */
  13108.  
  13109.  BOOL ColorInit(
  13110.     HANDLE      hInst )
  13111.  {
  13112.     char        szClass[80];      /* Class name */
  13113.     WNDCLASS    WndClass;         /* Class structure */
  13114.  
  13115.     /* Get class name */
  13116.     LoadString( hInst, IDS_COLORCLASS, szClass, sizeof( szClass ) );
  13117.  
  13118.     /* Prepare registration */
  13119.     memset( &WndClass, 0, sizeof( WndClass ) );
  13120.     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
  13121.     WndClass.lpfnWndProc    = ColorWndProc;
  13122.     WndClass.cbWndExtra     = WE_EXTRA;
  13123.     WndClass.hInstance      = hInst;
  13124.     WndClass.hCursor        = LoadCursor( NULL, IDC_ARROW );
  13125.     WndClass.hbrBackground  = GetStockObject( GRAY_BRUSH );
  13126.     WndClass.lpszClassName  = szClass;
  13127.  
  13128.     /* Register */
  13129.     return RegisterClass( &WndClass );
  13130.  }
  13131.  
  13132.  /* Create a document window of a given color on the MDI desktop. It loads
  13133.  the appropriate menu, and accelerator table if the color is BLUE. It
  13134.  initializes color and shading in the window extra words.*/
  13135.  
  13136.  HWND ColorCreate(
  13137.     HWND        hwndParent,
  13138.     int         wType )
  13139.  {
  13140.     char        szClass[80];      /* Class name for documents */
  13141.     char        szTitle[80];      /* Title for this document */
  13142.     HANDLE      hAccel = NULL;    /* Accelerator for blue doc only */
  13143.     HANDLE      hInst;            /* Current instance handle */
  13144.     HMENU       hmenuChild;       /* Handle to document's menu */
  13145.     HWND        hwndChild;        /* Handle to document */
  13146.  
  13147.     /* Get important info */
  13148.     hInst = GetWindowWord( hwndParent, GWW_HINSTANCE );
  13149.     LoadString( hInst, IDS_COLORCLASS, szClass, sizeof( szClass ) );
  13150.     sprintf( szTitle, "%s%d", szTitles[wType], ++wCounts[wType] );
  13151.  
  13152.     switch( wType )
  13153.     {
  13154.     case COLOR_RED:
  13155.        hmenuChild = LoadMenu( hInst, "RedMenu" );
  13156.        break;
  13157.  
  13158.     case COLOR_GREEN:
  13159.        hmenuChild = LoadMenu( hInst, "GreenMenu" );
  13160.        break;
  13161.  
  13162.     case COLOR_BLUE:
  13163.        hmenuChild = LoadMenu( hInst, "BlueMenu" );
  13164.        hAccel = LoadAccelerators( hInst, "BlueAccel" );
  13165.        break;
  13166.     }
  13167.  
  13168.     /* Create */
  13169.     hwndChild = MdiChildCreateWindow( szClass,
  13170.        szTitle,
  13171.        WS_MDICHILD,
  13172.        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  13173.        hwndParent,
  13174.        hmenuChild,
  13175.        hInst,
  13176.        0L );
  13177.  
  13178.     /* Success? */
  13179.     if ( hwndChild )
  13180.     {
  13181.        SetWindowWord( hwndChild, WE_SHADE, IDM_100 );
  13182.        SetWindowWord( hwndChild, WE_COLOR, wType );
  13183.        MdiSetAccel( hwndChild, hAccel );
  13184.     }
  13185.  
  13186.     return hwndChild;
  13187.  }
  13188.  
  13189.  /* Handle messages for our documents.  WM_COMMAND messages arrive at this
  13190.  procedure just as if the menu were attached to this window.*/
  13191.  
  13192.  long FAR PASCAL ColorWndProc(
  13193.     HWND        hwndChild,
  13194.     unsigned    message,
  13195.     WORD        wParam,
  13196.     LONG        lParam )
  13197.  {
  13198.     char        szText[20];       /* Client area text */
  13199.     HBRUSH      hBrush;           /* Brush for filling document */
  13200.     PAINTSTRUCT Paint;            /* Paint structure */
  13201.     int         wColor;           /* Color of current document */
  13202.     int         wShade;           /* Shading of current document */
  13203.  
  13204.     switch ( message )
  13205.     {
  13206.     case WM_COMMAND:
  13207.        switch( wParam )
  13208.        {
  13209.        /* File menu */
  13210.        case IDM_SAVE:
  13211.        case IDM_SAVEAS:
  13212.        case IDM_PRINT:
  13213.           break;
  13214.  
  13215.        case IDM_CLOSE:
  13216.           PostMessage( hwndChild, WM_SYSCOMMAND, SC_CLOSE, lParam );
  13217.           break;
  13218.  
  13219.        case IDM_0:
  13220.        case IDM_25:
  13221.        case IDM_50:
  13222.        case IDM_75:
  13223.        case IDM_100:
  13224.           CheckMenuItem( MdiGetMenu( hwndChild ),
  13225.              SetWindowWord( hwndChild, WE_SHADE, wParam ),
  13226.              MF_UNCHECKED );
  13227.           CheckMenuItem( MdiGetMenu( hwndChild ),
  13228.              GetWindowWord( hwndChild, WE_SHADE ),
  13229.              MF_CHECKED );
  13230.           InvalidateRect( hwndChild, (LPRECT) NULL, TRUE );
  13231.           break;
  13232.        }
  13233.        break;
  13234.  
  13235.     case WM_ERASEBKGND:
  13236.        return TRUE;
  13237.  
  13238.     case WM_PAINT:
  13239.        wColor = GetWindowWord( hwndChild, WE_COLOR );
  13240.        wShade = GetWindowWord( hwndChild, WE_SHADE );
  13241.        BeginPaint( hwndChild, &Paint );
  13242.        hBrush = CreateSolidBrush( rgbColors[wColor][wShade - IDM_0]);
  13243.        FillRect( Paint.hdc, &Paint.rcPaint, hBrush );
  13244.        DeleteObject( hBrush );
  13245.        strcpy( szText, szShadings[wShade - IDM_0] );
  13246.        TextOut( Paint.hdc, 0, 0, szText, strlen( szText ) );
  13247.        EndPaint( hwndChild, &Paint );
  13248.        break;
  13249.     }
  13250.     return MdiChildDefWindowProc( hwndChild, message, wParam, lParam);
  13251.  }
  13252.  
  13253.  /* Handle the NEW dialog box.*/
  13254.  
  13255.  int FAR PASCAL MainDlgNew(
  13256.     HWND        hDlg,
  13257.     unsigned    message,
  13258.     WORD        wParam,
  13259.     LONG        lParam )
  13260.  {
  13261.     static int  iButton;          /* Keep track of radio buttons */
  13262.     int         iReturn = FALSE;  /* Return value */
  13263.  
  13264.     switch ( message )
  13265.     {
  13266.     case WM_INITDIALOG:
  13267.        SendMessage( hDlg, WM_COMMAND, DLGNEW_RED, 0L );
  13268.        iReturn = TRUE;
  13269.        break;
  13270.  
  13271.     case WM_COMMAND:
  13272.        switch( wParam )
  13273.        {
  13274.        case DLGNEW_RED:
  13275.        case DLGNEW_GREEN:
  13276.        case DLGNEW_BLUE:
  13277.           iButton = wParam;
  13278.           CheckRadioButton( hDlg, DLGNEW_RED, DLGNEW_BLUE, iButton );
  13279.           if ( HIWORD( lParam ) == BN_DOUBLECLICKED )
  13280.           {
  13281.              SendMessage( hDlg, WM_COMMAND, IDOK, 0L );
  13282.           }
  13283.           break;
  13284.  
  13285.        case IDOK:
  13286.           EndDialog( hDlg, iButton );
  13287.           break;
  13288.  
  13289.        case IDCANCEL:
  13290.           EndDialog( hDlg, 0 );
  13291.           break;
  13292.        }
  13293.        break;
  13294.     }
  13295.     return iReturn;
  13296.  }
  13297.  
  13298.  /* Handle the ABOUT dialog box.*/
  13299.  
  13300.  int FAR PASCAL MainDlgAbout(
  13301.     HWND        hDlg,
  13302.     unsigned    message,
  13303.     WORD        wParam,
  13304.     LONG        lParam )
  13305.  {
  13306.     int         iReturn = FALSE;  /* Return value */
  13307.  
  13308.     switch( message )
  13309.     {
  13310.     case WM_INITDIALOG:
  13311.        iReturn = TRUE;
  13312.        break;
  13313.  
  13314.     case WM_COMMAND:
  13315.        EndDialog( hDlg, TRUE );
  13316.        break;
  13317.     }
  13318.     return iReturn;
  13319.  
  13320.  }
  13321.  
  13322.  
  13323.  Planning and Writing a Multithreaded OS/2 Program with Microsoft C
  13324.  
  13325.   Richard Hale Shaw
  13326.  
  13327.  From a programmer's perspective,  OS/2 systems are a lot like a new
  13328.  programming language. In order to become fluent you have to begin learning
  13329.  the idiom by writing programs with it. In this article, we'll cover the
  13330.  highlights of setting up and installing the Microsoft C Version 5.1 compiler
  13331.  for OS/2 development and briefly look at the header files included with it.
  13332.  Then we'll take a closer look at the OS/2 Application Programming Interface
  13333.  (API) with the object of writing our first OS/2 program. Last, we'll begin
  13334.  to explore the world of multithreaded programming and produce a
  13335.  multithreaded version of the C programmer's much beloved HELLO.C program.
  13336.  
  13337.  Compiler Setup
  13338.  
  13339.  The first concern for installing Microsoft C 5.1 is to ensure that you have
  13340.  enough disk space. The minimum space needed for the protected mode version
  13341.  of the compiler is approximately 3.5Mb (which includes two libraries). If
  13342.  you install the full protected mode compiler, you'll need closer to 4.5Mb.
  13343.  If you're using the compiler to produce applications for both DOS and OS/2,
  13344.  you'll probably need over 6Mb. The Setup program can be instructed to
  13345.  install the portions of the compiler that you want.
  13346.  
  13347.  You must also decide which directory structure the compiler will use. We
  13348.  discussed the preferred OS/2 directory structure, shown in Figure 1, in  the
  13349.  first article of this series. You may recall that the \OS2\PBIN directory
  13350.  contains only protected mode executables; \OS2\RBIN holds only real mode
  13351.  programs; and \OS2\BIN contains bound executables, that is, programs that
  13352.  can operate under DOS and OS/2.
  13353.  
  13354.  If you install the compiler while you are running OS/2, you'll find that the
  13355.  Setup program recommends that you incorporate the traditional C compiler
  13356.  directories into the same structure, as shown in Figure 2. The \OS2\LIB
  13357.  directory will contain all compiler libraries, \OS2\INCLUDE will contain the
  13358.  header files, and \OS2\SOURCE will be used for ancillary documentation and
  13359.  source files. Note that the compiler will also install an alternative set of
  13360.  header files under the \OS2\INCLUDE\MT directory. These are headers for the
  13361.  multithreaded version of the library, which we'll discuss later in this
  13362.  article.
  13363.  
  13364.  Microsoft C 5.1 includes facilities for producing traditional DOS programs,
  13365.  OS/2 programs, and programs designed to execute under both environments
  13366.  (bound applications). To this end, one of the more significant features
  13367.  available when you install the compiler under OS/2 are combined libraries.
  13368.  
  13369.  Previous versions of the C compiler already include a plethora of libraries,
  13370.  including three floating-point libraries and a graphics library. When you
  13371.  add to these libraries the need to keep versions of them for each memory
  13372.  model used, plus libraries for OS/2 development, you will find yourself
  13373.  running out of disk space fast. You can, however, combine the basic library
  13374.  for each memory model with   the graphics library (if selected) and
  13375.  appropriate floating-point library. Combining the libraries allows you to
  13376.  keep only one library per memory model. The Installation/Setup program will
  13377.  do this automatically for you and will delete the leftover component
  13378.  libraries for you if you elect to do so.
  13379.  
  13380.  Another important option is the ability to create a dual-mode or bound
  13381.  compiler. This is a version of the compiler that will run under both OS/2
  13382.  and DOS. The Setup program will leave a copy of BINDC.CMD (BINDC.BAT if you
  13383.  installed the compiler while running DOS), which can be run to produce the
  13384.  bound version. We'll discuss the production of bound applications later, but
  13385.  for now keep in mind that a bound application is one that calls only the
  13386.  subset of API services known as Family Application Programming Interface
  13387.  (FAPI) functions. These are functions that are available under both DOS
  13388.  and OS/2. Although limiting yourself to these functions will restrict you
  13389.  from using OS/2 multitasking services (after all, DOS does not offer these
  13390.  services), it does ensure that a program will run without recompilation
  13391.  under both environments.
  13392.  
  13393.  BINDC will leave a bound version of the compiler in the directory of your
  13394.  choice when it finishes. Since it's a bound executable, it is recommended
  13395.  that you have BINDC place its output in the \OS2\BIN directory (where
  13396.  dual-mode programs are kept) and delete the original copies of the compiler.
  13397.  You won't need them, and they'll just take up disk space. The dual-mode
  13398.  version will be bigger than the original, but you'll find it simpler to use
  13399.  only the one version.
  13400.  
  13401.  The setup program produces two files: NEW-VARS.CMD and NEW-CONF.SYS. The
  13402.  NEW-VARS.CMD file contains the environment variable settings that should be
  13403.  in place when you run the compiler, based on the directory selections you
  13404.  made during installation. You can include these either in STARTUP.CMD or in
  13405.  the CMD file used for setting up your C development session through the OS/2
  13406.  Program Starter [see "Using the OS/2 Environment to Develop DOS and OS/2
  13407.  Applications," MSJ (Vol. 4, No. 1)]. NEW-CONF.SYS contains the additions
  13408.  that ought to be added to CONFIG.SYS (CONFIG.OS2 in older, dual-boot
  13409.  environments).
  13410.  
  13411.  New Compiler Options
  13412.  
  13413.  There are three compiler command-line options that are pertinent to OS/2
  13414.  programming. The /Lr option designates the compilation of a conventional
  13415.  real mode executable (the default in earlier versions of the compiler). The
  13416.  /Lp option, however, signals the compiler to compile and link for the
  13417.  protected mode. When you use this switch, the protected mode version of a
  13418.  given library will be used. For instance, if a conventional, real mode,
  13419.  small mem-ory model compilation used SLIBCE.LIB (the small model combined
  13420.  library with floating-point emulation), adding the /Lp option instructs the
  13421.  linker to use SLIBCEP.LIB (the protected mode version of the library).
  13422.  
  13423.  The third option, /Fb, directs the linker to run BIND.EXE after linking is
  13424.  complete. BIND is a utility that converts protected mode programs into
  13425.  dual-mode applications, which can be run in either real or protected mode.
  13426.  With these options in mind, and using the directory structure discussed
  13427.  earlier, we can construct a generic MAKE file for which we use MAKE to build
  13428.  our first OS/2 programs (shown in Figure 3).
  13429.  
  13430.  This MAKE file, which is called HELLO (since it will be used to compile
  13431.  HELLO.C), forms the basis for the MAKE files we'll use to build other
  13432.  programs. It sets up the environment variables INCLUDE and LIB for the
  13433.  compiler and passes a number of options to CL. Note that the MAKE file uses
  13434.  both /Lp and /Fb to instruct the compiler to produce a protected mode bound
  13435.  version of the program.
  13436.  
  13437.  The /W3 option forces the compiler to use warning level 3, the most severe
  13438.  warning level available from the compiler. This option is very useful to
  13439.  ensure strict type checking of different objects and arguments against
  13440.  OS/2-type definitions. The /Zpe option combines two options: /Zp and /Ze.
  13441.  The former instructs the compiler to produce packed, or byte-aligned (as
  13442.  opposed to word-aligned), structures. The latter allows extensions to C,
  13443.  particularly the far, near, and pascal keywords, which OS/2 programs will
  13444.  reference frequently. The /Ox option turns on maximum optimization, and the
  13445.  /I option specifies the INCLUDE directory, which the compiler will use.
  13446.  
  13447.  The /G2 option forces the production of code for the 80286, which is a handy
  13448.  option, since 286 instructions are generally more efficient than pure 8086
  13449.  instructions. Although this option restricts the program to run on either an
  13450.  80286 or above, it's not a problem in the OS/2 environment, since OS/2 will
  13451.  only run on a machine with an 80286 or 80386 (the 80386 also executes 80286
  13452.  code). However, it could cause problems if the bound executable is run under
  13453.  DOS on an 8088/8086.
  13454.  
  13455.  Several additional points should be made about this MAKE file. First, note
  13456.  that for debugging purposes we will want to include symbolic debugging
  13457.  information with the /Zi option and turn off optimization with /Od. Thus,
  13458.  I've included the additional line of options for debugging. You can enable
  13459.  these options and disable the others by removing the # in front of one line
  13460.  and inserting a # in front of the other line of options (# is the comment
  13461.  marker in MAKE files). Also, note the use of the /NOE option that CL passes
  13462.  to the linker, which prevents the linker's extended library search. This
  13463.  option will ensure that the correct versions of the library functions are
  13464.  linked to the program.
  13465.  
  13466.  Figure 3:
  13467.  
  13468.  #file: hello
  13469.  #generic OS/2 MAKE file for producing 'bound' executables
  13470.  #
  13471.  #Currently set for making HELLO.C
  13472.  #Usage: C>make hello
  13473.  
  13474.  INCLUDE=\os2\include
  13475.  LIB=\os2\lib
  13476.  COPT=/Lp /Fb /W3 /Zpe /G2 /Ox /I$(INCLUDE)
  13477.  #COPT=/Lp /Fb /W3 /Zpie /G2 /Od /I$(INCLUDE)
  13478.  
  13479.  hello.exe: hello.c hello
  13480.      cl $(COPT) hello.c /link /noe
  13481.  
  13482.  #end
  13483.  
  13484.  OS/2 System Calls
  13485.  
  13486.  OS/2 system services are available to application programs through the OS/2
  13487.  API. Unlike the interrupt-based interface of DOS, the API makes all services
  13488.  available through system calls (also known as call-gates). While the DOS
  13489.  interface was limited to 256 functions (via Int 21h), there is no limit on
  13490.  the number of services that could be added to OS/2 in the future. And
  13491.  whereas DOS services required the use of registers to receive or return
  13492.  values, OS/2 system services pass these on the stack. Thus, all OS/2
  13493.  services use the same format: if a system service is capable of returning
  13494.  error values, it will return a zero when successful and an unsigned nonzero
  13495.  integer on error. In addition, as stated earlier, the subset of API
  13496.  functions known as Family API (FAPI) functions will operate under both OS/2
  13497.  and DOS, allowing you to write bound executable programs that operate in
  13498.  both environments.
  13499.  
  13500.  When you include a call to an API function in your OS/2 program, the linker
  13501.  does not add the code for the function to the executable program as it would
  13502.  for a DOS program. Instead, it will add instructions for loading the
  13503.  function's code from the appropriate OS/2 dynamic-link library (DLL). When
  13504.  the program executes, OS/2 will load the necessary DLLs (if it hasn't
  13505.  already loaded them for some other application program). Thus, the functions
  13506.  will be available to the program at run time.
  13507.  
  13508.  Only one instance of an OS/2 API function will be loaded into memory, even
  13509.  if more than one application program is using it. Note that for FAPI
  13510.  functions in a bound executable program, the linker will include both the
  13511.  DLL calling code and the real mode executable code for the function. The
  13512.  former will be used when the program is operating in the OS/2 protected
  13513.  mode, and the latter will invoke Int 21h when in MS-DOS real mode.
  13514.  
  13515.  Since OS/2 loads API routines from a DLL, they are located in a different
  13516.  segment from that of the calling routine and must be reached with a FAR
  13517.  call. There are four general types of parameters that can be passed to an
  13518.  API system service: byte (or char), word (or unsigned), double word
  13519.  (unsigned long), and address (far pointer). If the service requires only the
  13520.  value of the parameter, then a copy of it is passed (call by value). If the
  13521.  service requires a pointer to a parameter, however, the address is passed to
  13522.  the service (call by reference). Because the calls are FAR calls, addresses
  13523.  passed must also be FAR. To ensure that you are passing a far address
  13524.  correctly to a system service, you should declare the object as far, use a
  13525.  cast, or compile with the large data model. Meticulous use of the OS/2
  13526.  object definitions found in the new header files (described below) will
  13527.  eliminate most problems.
  13528.  
  13529.  You might also note that segment values that are found in far addresses are
  13530.  really selectors. Although they have the same segment:offset form found in
  13531.  far addresses under MS-DOS real mode, OS/2's protected mode requires a
  13532.  selector value. A selector is actually an index into a table of segment
  13533.  addresses and has no correspondence to a physical segment. Selectors must be
  13534.  used since OS/2's virtual memory management may change the physical segment
  13535.  location as it is moved around in memory or swapped to disk. The layer of
  13536.  abstraction they provide allows you to address a far object without having
  13537.  to know its real physical location in memory--or whether it's in memory
  13538.  at all (taken care of for you by OS/2).
  13539.  
  13540.  API functions use the Pascal calling convention. This convention specifies
  13541.  that there be a fixed number of arguments to the function and that the
  13542.  arguments are pushed on the stack left to right (the order in which you
  13543.  specify them in your source code). In addition, Pascal calls do not require
  13544.  the function being called to clean up the stack. All these requirements
  13545.  result in smaller, faster function calls. This scheme is the reverse of the
  13546.  traditional C calling convention, which pushes arguments right to left
  13547.  (allowing a variable number of arguments); requires the caller to clean up
  13548.  the stack; and generally produces slower, larger code. The OS/2 header files
  13549.  discussed below specify API functions as far pascal calls, so most of the
  13550.  time you will not have to take any steps beyond including the appropriate
  13551.  header files in your program. The far pascal convention is defined in the
  13552.  OS/2 header files as APIENTRY.
  13553.  
  13554.  Additionally, API functions use the Microsoft(R) Windows convention of
  13555.  two- or three-phrase descriptive names with both uppercase and lowercase
  13556.  letters. Keyboard subsystem services are provided by functions whose names
  13557.  begin with Kbd; Mouse and Video subsystem services names begin with Mou and
  13558.  Vio respectively. The names of all remaining operating system services (OS/2
  13559.  kernel calls) begin with Dos. Some brief examples of these include DosRead,
  13560.  DosCreateThread, KbdCharIn, and VioWrtTTY.
  13561.  
  13562.  The documentation for the OS/2 API functions can be found in the OS/2
  13563.  Programmer's Reference. This manual is a part of the Microsoft OS/2
  13564.  Programmer's Toolkit and the Microsoft OS/2 Software Development Kit.
  13565.  
  13566.  Header Files
  13567.  
  13568.  Microsoft C 5.1 provides six new header files, some or all of which need to
  13569.  be included in programs that take advantage of the OS/2 API. These header
  13570.  files (listed in Figure 4) provide the API declarations, function
  13571.  prototypes, macros, constants, and type definitions needed by an OS/2 C
  13572.  program. As a routine part of getting started programming under OS/2, you
  13573.  should become intimately familiar with their contents. You'll find that most
  13574.  of the type definitions and structures use a naming convention similar to
  13575.  that used by the system services described earlier. Also, since many system
  13576.  services will place return values in the structures defined in these header
  13577.  files, OS/2 programming will be easier later if you study their contents
  13578.  now.
  13579.  
  13580.  The OS/2 header files are hierarchically nested, so that you need only
  13581.  include OS2.H in your program most of the time. The remaining header files
  13582.  and definitions can be included by using various combinations of control
  13583.  macros (listed in Figure 5), which should be defined in your program before
  13584.  including OS2.H. This is particularly helpful when you are using only a
  13585.  small subset of API functions in your program, since the headers are large
  13586.  and compilation will proceed more quickly if you include only what the
  13587.  program requires.
  13588.  
  13589.  OS2.H itself includes two header files. The first file, OS2DEF.H, contains
  13590.  most of the commonly used definitions, typedefs, macros, constants, and
  13591.  structures. The second, BSE.H, indirectly contains the base definitions for
  13592.  the various OS/2 subsystems (Keyboard, Video, Mouse, and Dos), plus
  13593.  error-handling macros by optionally including three additional header files:
  13594.  BSEDOS.H, BSESUB.H, and BSEERR.H.
  13595.  
  13596.  BSEDOS.H contains definitions required for using the OS/2 kernel system
  13597.  service, and can be included by defining INCL_DOS before the #include for
  13598.  OS2.H in your program. BSESUB.H contains all the definitions required for
  13599.  using any of the OS/2 subsystems (Kbd, Mou or Vio) and is included by
  13600.  defining INCL_SUB. BSEERR.H contains all error-related macros and is
  13601.  included through INCL_DOSERRORS. If necessary, you can force the blanket
  13602.  inclusion of all API declarations and definitions by defining INCL_BASE in
  13603.  your program prior to the #include for OS2.H:
  13604.  
  13605.  #define    INCL_BASE
  13606.  #include   <os2.h>
  13607.  
  13608.  Note that many commonly used components will be defined by default unless
  13609.  you define INCL_NOCOMMON in your program. You can also include specific
  13610.  kernel services components by defining the macros listed in Figure 5.
  13611.  
  13612.  Figure 4:
  13613.  
  13614.  OS2.H     - Always #included in your program
  13615.  OS2DEF.H     - Common definitions
  13616.  BSE.H     - Base definitions, #includes the following:
  13617.  
  13618.  BSEDOS.H    - Kernel services definitions
  13619.  BSESUB.H     - Kbd, Vio, Mou definitions
  13620.  BSEERR.H     - Error macros
  13621.  
  13622.  Figure 5:
  13623.  
  13624.  Define this macro:    To include definitions/declarations for:
  13625.  
  13626.  INCL_BASE    All services
  13627.  INCL_DOS    Kernel services
  13628.  INCL_SUB    Subsystem (Kbd, Vio, Mou)
  13629.  INCL_DOSERRORS    Error macros
  13630.  INCL_DOSPROCESS    Processes and threads calls
  13631.  INCL_DOSINFOSEG    Information segment calls
  13632.  INCL_DOSFILEMGR    File management calls
  13633.  INCL_DOSMEMMGR    Memory management calls
  13634.  INCL_DOSSEMAPHORES    Semaphore functions
  13635.  INCL_DOSDATETIME    Date/Time and Timer calls
  13636.  INCL_DOSMODULEMGR    Module management services
  13637.  INCL_DOSNLS    National language services
  13638.  INCL_DOSSIGNALS    Signal functions
  13639.  INCL_DOSMONITORS    Monitor services
  13640.  INCL_DOSSESMGR    Session management calls
  13641.  INCL_DOSDEVICES    Device and IOPL services
  13642.  INCL_DOSQUEUES    Queue functions
  13643.  INCL_RESOURCES    Resource-support functions
  13644.  
  13645.  
  13646.  A First OS/2 Program
  13647.  
  13648.  Once you have successfully installed the compiler, there is nothing to keep
  13649.  you from writing your first OS/2 program. Figure 6 lists the code for an
  13650.  OS/2 version of Dennis Ritchie's famous HELLO.C.
  13651.  
  13652.  At first glance, an OS/2 program such as this version of HELLO.C might
  13653.  appear extremely bizarre. It certainly does not resemble the Kernighan and
  13654.  Ritchie version that we've all come to know and love. Nevertheless, it
  13655.  accomplishes many of the same purposes. It allows us to begin to explore the
  13656.  OS/2 API; introduces us to a real use of some of the header files, defines,
  13657.  and functions; and most of all, gets us started writing our first real OS/2
  13658.  program.
  13659.  
  13660.  A second glance will reveal the familiar structure so often used to write
  13661.  maintainable, efficient C programs. You'll note the use of INCL_SUB and
  13662.  INCL_DOS to include function prototypes for the single call to the Vio
  13663.  subsystem and the single kernel call in the program. The printf call has
  13664.  been replaced with a call to VioWrtTTY, which prints a string on the logical
  13665.  screen used by our program (all video access in OS/2 is done through a
  13666.  logical screen group). Note that this function requires both the address of
  13667.  the string and the string length and that it appropriately handles the
  13668.  C escape sequences \r and \n, to generate a carriage-return/line-feed
  13669.  combination. Also note that all Vio calls require a zero for their last
  13670.  parameter.
  13671.  
  13672.  After printing the string, a kernel call to DosExit terminates the entire
  13673.  program. The first parameter specifies whether the entire process or the
  13674.  current thread should be terminated (more on this in the next section). The
  13675.  second parameter is the exit code, which is passed to the parent process and
  13676.  is identical to the one that is passed in exit, the traditional C
  13677.  termination function.
  13678.  
  13679.  Finally, note that this program can be compiled, linked, and bound to create
  13680.  a dual-mode application. Thus, the same executable program will run,
  13681.  unmodified, under either OS/2 or DOS. Note that the MAKE file for this
  13682.  program can be found in Figure 3. If you haven't already compiled and run a
  13683.  C program using API calls, I suggest you type in HELLO.C, compile it, and
  13684.  run it. It will certainly help make OS/2's magic more real and prepare you
  13685.  for our next step: the world of multithreaded programs.
  13686.  
  13687.  Figure 6:
  13688.  
  13689.  /* hello.c RHS 10/14/88
  13690.   *
  13691.   * 1988 OS/2 version of    K&R's hello.c
  13692.   */
  13693.  
  13694.  #define    INCL_SUB        /* for Vio calls used */
  13695.  #define    INCL_DOS        /* for DosExit call used */
  13696.  #include   <stdio.h>
  13697.  #include   <string.h>
  13698.  #include   <os2.h>
  13699.  
  13700.  void main(void);        /* function prototype */
  13701.  
  13702.  void main(void)
  13703.      {
  13704.      char *hello_str = "Hello, world!\r\n";
  13705.      int    len = strlen(hello_str);
  13706.  
  13707.      VioWrtTTy(hello_str,len,0);    /* print the message */
  13708.      DosExit(EXIT_PROCESS,0);       /* exit the program */
  13709.      }
  13710.  
  13711.  Multiple Threads
  13712.  
  13713.  The single most significant difference between OS/2 and DOS are the former's
  13714.  facilities for multitasking. Multitasking will dramatically increase the
  13715.  efficiency of most applications that are designed to take advantage of it.
  13716.  However, not only do OS/2's facilities allow for the simultaneous execution
  13717.  of more than one program, but what's more they permit OS/2 to execute
  13718.  different parts of the same program at the same time.
  13719.  
  13720.  The OS/2 multitasking model, shown in Figure 7, is built of threads,
  13721.  processes, and screen groups. A thread is the smallest unit of execution, a
  13722.  piece of code dispatched by the system. Threads are organized into a
  13723.  process, or the portion of a program that controls the ownership of
  13724.  resources, such as files, memory, and threads. A process is composed of at
  13725.  least one thread and may consist of as many as 255 separate threads. A
  13726.  process's main thread is the one in which execution begins. Note that though
  13727.  a process may use a thread to manage a resource, a thread by itself does not
  13728.  own any resources. A thread inherits the environment (open files, and so on)
  13729.  of which it is a part and shares the same code and data segments as its
  13730.  parent process. Collectively, the processes that share the same logical
  13731.  keyboard and screen are a part of the same screen group.
  13732.  
  13733.  The model allows for multitasking at all three levels (screen group,
  13734.  process, and thread). In this article, however, we're primarily concerned
  13735.  with the simultaneous execution of threads within the same process. Programs
  13736.  that execute in such a manner are called multithreaded programs.
  13737.  
  13738.  Multiple Thread Execution
  13739.  
  13740.  OS/2 lets concurrently executing threads share a single
  13741.  computer's CPU through the use of a preemptive, priority-based task
  13742.  scheduler (later editions of OS/2 will take advantage of multiple-CPU
  13743.  architectures). In reality, only one thread at a time is executing, but the
  13744.  CPU's attention turns so quickly from one thread to another that the threads
  13745.  appear to be executing at the same time--as long as the applications
  13746.  that use multiple threads do not abuse system resources and CPU time in
  13747.  their multithreaded code.
  13748.  
  13749.  The task scheduler controls which thread gets slices of CPU time and how
  13750.  much time is doled out to the thread. A thread's priority controls its
  13751.  access to the CPU (if it has a higher priority, it will get more CPU time
  13752.  relative to other threads). Thus, a previously idle, higher priority thread
  13753.  that is ready to run can preempt CPU time from a lower priority thread that
  13754.  is currently executing. On the other hand, a lower priority thread must wait
  13755.  until all higher priority threads are idle before the scheduler will give it
  13756.  CPU time.
  13757.  
  13758.  The OS/2 task scheduler employs three categories of priorities for
  13759.  scheduling tasks. The highest priority is time-critical, which should be
  13760.  used by tasks that must respond to some type of regularly occurring event
  13761.  (like a communications stream or keyboard input). The second category,
  13762.  regular, is the default priority for a new thread and should be used by most
  13763.  normal threads. The last category, idle, is for threads that should execute
  13764.  when there are no higher priority tasks that are ready or able to execute
  13765.  (such as a print spooler). Note that there are 32 levels of priorities in
  13766.  each category and that the default priority for foreground processes is
  13767.  regular, level 0, whereas processes that run in the background are also
  13768.  regular but have a lower priority level. In general, foreground tasks are
  13769.  given a higher priority than background tasks.
  13770.  
  13771.  The task scheduler always runs the highest priority thread that is capable
  13772.  of executing. If two or more threads with the same priority are ready to
  13773.  run, the scheduler will evenly grant them CPU time on a round-robin basis.
  13774.  If a thread is blocked--that is, it is waiting until some event
  13775.  occurs--OS/2 will suspend it and run another thread. Note that the
  13776.  TIMESLICE= statement in CONFIG.SYS controls the minimum and the maximum time
  13777.  slice values used by OS/2.
  13778.  
  13779.  As mentioned above, a thread is blocked when it is waiting for an event to
  13780.  occur. A thread is running when it is being given CPU time slices. If a
  13781.  thread is no longer blocked but hasn't yet been given CPU time slices, then
  13782.  it is said to be ready to run.
  13783.  
  13784.  Planning a Multithreaded Program
  13785.  
  13786.  As mentioned above, a program or process can have more than one thread of
  13787.  execution. Using multiple threads lets it manipulate and control machine
  13788.  resources more efficiently than would a single-threaded application. For
  13789.  instance, printing, communications file transfers, and database sorting all
  13790.  are tasks that can be performed simultaneously by separate threads of
  13791.  execution while the main thread of an application program continues to serve
  13792.  the end user. Furthermore, in the multitasking environment of OS/2, a
  13793.  multiple-thread architecture is essential to help ensure that no single task
  13794.  will hog machine resources. Gordon Letwin, OS/2 architect, first identified
  13795.  this libertarian approach to sharing resources: programs must obey the rules
  13796.  in order to work together. This approach makes it obvious who the violator
  13797.  is when a program abuses the environment, and permits the system to operate
  13798.  in the most efficient manner possible.
  13799.  
  13800.  Thus, planning a multithreaded application presupposes that the tasks that
  13801.  the program will perform can best be implemented by using multiple threads
  13802.  of execution. There are a number of caveats that help in planning such a
  13803.  program, which can be summarized as: never assume that OS/2 will execute a
  13804.  multitasking program or routine in a specific way. Corollaries of this rule
  13805.  include:
  13806.  
  13807.  ■    Never assume that one routine will execute before another.
  13808.  
  13809.  ■    Never assume that a given routine will execute for a given number of
  13810.  milliseconds.
  13811.  
  13812.  ■    Never assume that future versions of OS/2 will schedule tasks the same
  13813.  way the current one does.
  13814.  
  13815.  ■    Never assume that different threads will always be competing for CPU
  13816.  time. Future versions of OS/2 will run on parallel processors, allowing
  13817.  different threads to execute simultaneously. While you and I both know that
  13818.  they don't really execute concurrently at this point, treat them as if they
  13819.  already do.
  13820.  
  13821.  ■    Never assume any direct correlations between CPU time slices and CPU
  13822.  cycles.
  13823.  
  13824.  ■    Never assume that OS/2 can guarantee which thread will execute first at
  13825.  any point during the course of your program. Although the main thread is
  13826.  always the first thread to execute in the program, there are no guarantees
  13827.  on execution order once the second thread has begun.
  13828.  
  13829.  Obviously, the last caveat doesn't mean that there aren't any controls
  13830.  available for manipulating events or serializing access to resources among
  13831.  multiple threads: that's where semaphores and priority levels come in. We'll
  13832.  discuss these later, but for now remember that when writing a multithreaded
  13833.  application, never assume!
  13834.  
  13835.  A Multithread Program
  13836.  
  13837.  The process of creating an additional thread is fairly simple in itself. For
  13838.  instance, suppose you wanted an application to run uninterrupted, ignoring
  13839.  all keyboard input until the user presses the Esc key, at which point the
  13840.  application would terminate. In a DOS application, there are two possible
  13841.  solutions: you could design the program to occasionally poll the keyboard,
  13842.  which is cumbersome in a complex application, or your program could trap
  13843.  the BIOS keyboard interrupt with code that would signal the main program if
  13844.  the Esc key is pressed.
  13845.  
  13846.  Under OS/2 these approaches are neither necessary nor relevant. Instead, you
  13847.  can start a thread that blocks on keyboard input (that is, it waits until
  13848.  there is keyboard activity). When the user presses a key, the thread
  13849.  examines the key. If the key is any key other than Esc, it will continue to
  13850.  block. If the key is Esc, the thread will terminate the entire process.
  13851.  
  13852.  A brief examination of the program shown in Figure 8 will make this
  13853.  procedure clearer. The main thread begins where all C programs begin, with
  13854.  the call to main. The main thread creates the keyboard thread with the call
  13855.  to the API kernel function, DosCreateThread.
  13856.  
  13857.  Note that DosCreateThread takes several parameters, as shown by the function
  13858.  prototype in Figure 9. The first parameter is the address of a function that
  13859.  contains the code for the thread. Here, the function is innocently named
  13860.  keyboard_thread. The second parameter is the address of a variable into
  13861.  which OS/2 will place the thread's identifier once it has successfully
  13862.  created the thread. The final parameter is the address of the top of the
  13863.  stack allocated for the thread, which should be at least 512 bytes in size.
  13864.  Note that in order to pass the address of the top of the stack area,
  13865.  keythreadstack, we must pass the address of the last byte of keythreadstack
  13866.  in the manner shown.
  13867.  
  13868.  As mentioned earlier, all API functions return nonzero on failure, so we
  13869.  know that a new thread was successfully created if DosCreateThread returns a
  13870.  zero value. The newly created thread will immediately begin execution of the
  13871.  code found in keyboard_thread. Although the call to DosCreateThread could
  13872.  have been placed almost anywhere, calling it early in the program ensures
  13873.  that the program will terminate immediately if the user presses the Escape
  13874.  key. Note that the while(TRUE) statement can be replaced with whatever code
  13875.  you would use for processing in the main thread.
  13876.  
  13877.  Now let's take a look at the keyboard_thread function. The code for a thread
  13878.  should always be contained in a single function. You cannot incorporate the
  13879.  code for this function into main or any other function, nor can you call a
  13880.  thread function directly. Thus, thread functions are really an OS/2
  13881.  extension to C.
  13882.  
  13883.  Note that keyboard_thread begins executing immediately after creation and
  13884.  then goes into the loop to call the Kbd subsystem function, KbdCharIn. This
  13885.  function's first parameter is the address of an OS/2 KBDKEYINFO structure,
  13886.  which will contain information about the keys pressed upon return (this
  13887.  structure will be discussed in detail in an upcoming installment of this
  13888.  series). The chChar member of the structure will contain the ASCII value of
  13889.  the key pressed. The second parameter specifies how long the function should
  13890.  wait until the user presses a key. Finally, passing IO_WAIT will cause the
  13891.  function to wait for  the key indefinitely, in turn causing OS/2 to suspend
  13892.  the calling thread until that thread's screen group receives a key from the
  13893.  user.
  13894.  
  13895.  Once the user presses a key, OS/2 will wake up the thread and return from
  13896.  the KbdCharIn call. The thread then examines the key and breaks out of the
  13897.  loop if it is the Esc key, calling DosExit to terminate the entire process.
  13898.  You might note that the thought behind the function is similar to
  13899.  object-oriented programming: the keyboard_thread function completely
  13900.  encapsulates the keyboard control and program termination sequence. The main
  13901.  program doesn't have to know how it works or what it does--that's taken
  13902.  care of for it by the thread itself.
  13903.  
  13904.  A program's main thread must be kept alive until all other thread activity
  13905.  has finished or can be terminated. When the main thread dies, the other
  13906.  threads die. Thus, if you insert code in the main thread that will exit the
  13907.  process, the keyboard thread (and any other threads) will be destroyed with
  13908.  the main thread. If a secondary thread like the keyboard thread shown here
  13909.  reaches the end of its code without calling a termination routine, it will
  13910.  die, but it will not affect other threads. Threads can explicitly terminate
  13911.  themselves by means of a call to DosExit:
  13912.  
  13913.  DosExit(EXIT_THREAD,
  13914.  term_code);
  13915.  
  13916.  or a thread can terminate the entire process via:
  13917.  
  13918.  DosExit(EXIT_PROCESS,
  13919.  term_code);
  13920.  
  13921.  The MAKE file for this example can be found in Figure 10. The /Gs or /G2s
  13922.  options are used to turn off standard run-time stack checking, since the
  13923.  run-time stack checks will report false stack overflow errors in code for
  13924.  the thread. If, however, you wish to isolate this to the thread code itself,
  13925.  you can insert a #pragma:
  13926.  
  13927.  #pragma check_stack(off)
  13928.  /* thread function is
  13929.  placed here */
  13930.  #pragma check_stack(on)
  13931.  
  13932.  which will turn off run-time stack checking for the code inserted between
  13933.  the #pragmas.
  13934.  
  13935.  If the main thread needs to close files or shut down other processes or
  13936.  resources before terminating, the thread function could set a semaphore (as
  13937.  a flag) that could be checked occasionally by the main thread, instead of
  13938.  terminating outright. Another alternative would be to use DosExitList, shown
  13939.  in Figure 11.
  13940.  
  13941.  Figure 8:
  13942.  
  13943.  /*
  13944.   * Simple keyboard thread example
  13945.   *
  13946.   * This program illustrates how a process might start
  13947.   * a keyboard thread which will terminate the process when
  13948.   * the user presses the Esc key.
  13949.   *
  13950.   * The program starts a keyboard thread which blocks on keyboard
  13951.   * input and terminates the entire program when the user presses
  13952.   * the Esc key.  All other keys are ignored and thrown away.
  13953.   */
  13954.  
  13955.  #define    INCL_DOS
  13956.  #define    INCL_SUB
  13957.  
  13958.  #include   <os2.h>
  13959.  #include   <mt\stdio.h>
  13960.  #include   <mt\process.h>
  13961.  
  13962.  #define    ESC    0x1b
  13963.  #define    TRUE    1
  13964.  #define    THREADSTACK    512
  13965.  
  13966.  char keythreadstack[THREADSTACK];
  13967.  
  13968.  void keyboard_thread(void);
  13969.  void main(void);
  13970.  
  13971.  void main(void)
  13972.      {
  13973.      TID    threadid;
  13974.  
  13975.      if(DosCreateThread(keyboard_thread, &threadid,
  13976.              &keythreadstack[THREADSTACK-1]))
  13977.          exit(-1);
  13978.  
  13979.      while(TRUE)    /* replace this with
  13980.            ;           code for main program */
  13981.      }
  13982.  
  13983.  
  13984.  void keyboard_thread(void) /* keyboard thread code */
  13985.      {
  13986.      KBDKEYINFO keyinfo;
  13987.  
  13988.      while(TRUE)
  13989.          {
  13990.          KbdCharIn(&keyinfo,IO_WAIT,0);  /* wait for keystroke */
  13991.          if(keyinfo.chChar == ESC)     /* if ESC pressed, break */
  13992.              break;
  13993.          }
  13994.      DosExit(EXIT_PROCESS,0);          /* terminate the process */
  13995.      }
  13996.  
  13997.  Figure 9:
  13998.  
  13999.  unsigned DosCreateThread(void (far *) functionptr(void),
  14000.                           TID *threadidptr, void *stack);
  14001.  
  14002.  
  14003.  Reentrance Issues
  14004.  
  14005.  There is one important constraint to consider when writing multiple-thread
  14006.  programs. It is the problem encountered when more than one thread tries to
  14007.  simultaneously execute code that is being used by another thread. This
  14008.  problem does not arise with the OS/2 API functions: their code has been
  14009.  written for a multitasking environment. The standard C library and your own
  14010.  functions, however, are another matter.
  14011.  
  14012.  Consider a scenario like the following: The standard library routine printf
  14013.  uses an internal buffer to format the characters that it will write to the
  14014.  standard output. Suppose one thread is in the middle of executing printf,
  14015.  with half of the buffer formatted, when another thread begins to execute
  14016.  printf's code, overwriting the buffer with its own characters--and
  14017.  resulting in chaos. This behavior is, of course, intolerable, and unless
  14018.  you're going to implement your own set of semaphores to control the use of
  14019.  every library routine (which is not a viable solution), you're going to end
  14020.  up with a mess on your hands.
  14021.  
  14022.  There are, fortunately, two solutions to this problem. First, when writing
  14023.  multithread programs, refrain from using any standard library routines in
  14024.  any but the main thread. This guarantees that only one thread will be using
  14025.  the standard library routines at a time. With the exception of a few
  14026.  reentrant routines, the standard library routines are not reentrant and are
  14027.  designed for single-threaded execution (see Figure 12). If you must control
  14028.  access to a specific routine from the standard library (or one of your own
  14029.  routines), there are two ways you can do it:
  14030.  
  14031.  ■    Use the DosEnterCritSec API call to temporarily freeze the other
  14032.  threads. Although this approach does work, it isn't the best solution and is
  14033.  mentioned here for informational purposes; there are too many things that
  14034.  can go wrong.
  14035.  
  14036.  ■    Use a semaphore to control access to a function. This solution is more
  14037.  practicable, since only the threads that  are trying to access the shared
  14038.  code will be affected--the rest will continue to execute
  14039.  (DosEnterCritSec, on the other hand, will freeze all other threads).
  14040.  
  14041.  If you find it impossible to live without the standard library functions,
  14042.  there is one additional alternative: the multithreaded standard library.
  14043.  While earlier versions of the C compiler required that you distinguish
  14044.  between reentrant and nonreentrant functions, Microsoft now supports a
  14045.  version of the standard library, LLIBCMT.LIB, that is completely reentrant
  14046.  and supports multiple threads. If you write programs that use this library,
  14047.  you must use the new _beginthread and _endthread functions that are
  14048.  contained in the library (instead of using DosCreateThread and DosExit).
  14049.  (For more information, see the sidebar "Using the Multithreaded Library:
  14050.  DosCreateThread vs. _beginthread.")
  14051.  
  14052.  Finally, you can write your own functions to accommodate multiple threads.
  14053.  If you choose to do so, there are at least three key guidelines to follow.
  14054.  First, multithreaded functions cannot disable interrupts or issue an INT
  14055.  instruction. Second, they should not alter the contents of a segment
  14056.  register or perform segment manipulations. Last, there must be strict
  14057.  controls on access to global or static data by functions that can be called
  14058.  by multiple threads (alluded to earlier in the discussion of the standard
  14059.  library printf routine). The preferred mechanism for incorporating these
  14060.  controls into your program is OS/2 semaphores.
  14061.  
  14062.  Thread Control
  14063.  
  14064.  A detailed discussion of the use of OS/2 semaphores can be found in "Using
  14065.  OS/2 Semaphores to Coordinate Concurrent Threads of Execution," MSJ (Vol. 3,
  14066.  No. 3), but we will briefly reiterate some of the points made in that
  14067.  article that are pertinent here.
  14068.  
  14069.  Although OS/2 offers several facilities for interprocess communication
  14070.  (pipes, queues, signals, and shared memory), semaphores are the preferred
  14071.  method of coordinating multiple threads. You can use them to serialize
  14072.  access to pieces of code or resources that cannot be shared. Alternatively,
  14073.  you can use semaphores when you need to have one thread signal to another
  14074.  that an event has occurred. Of the several types of semaphores OS/2 offers,
  14075.  RAM semaphores (used by threads in the same process) are the simplest to
  14076.  implement and are the easiest to deal with in terms of our first
  14077.  multithreaded program.
  14078.  
  14079.  The MS-DOS operating system is a single-tasking environment: only one thread
  14080.  operates at a time. A program executing under DOS has considerable control
  14081.  over a resource, including the ability to disable interrupts and ensure
  14082.  uninterrupted access to it. Signaling between processes in the DOS
  14083.  environment is easy, since a global variable or flag can be used to
  14084.  coordinate different pieces of code. You can have one process wait while the
  14085.  flag is set, that is, until another process clears the flag. Once the flag
  14086.  has been cleared, the process continues, setting the flag for itself, safe
  14087.  in the knowledge that it alone has access to a particular resource,
  14088.  including the flag variable used for signaling.
  14089.  
  14090.  Under OS/2, this type of signaling is not possible, since there is no way
  14091.  (without controls) to guarantee the order in which threads will execute.
  14092.  Further, one thread may be reading a flag while another may be setting or
  14093.  clearing it, or a second thread might end up setting the flag between the
  14094.  moment that the first thread stopped waiting and began to set the flag
  14095.  itself. Therefore, more than one thread might end up with access to the same
  14096.  resources. The outcome of such a situation is predictably disastrous.
  14097.  Furthermore, continually checking the value of such a flag uses the CPU
  14098.  unnecessarily, lowering the efficiency of the system.
  14099.  
  14100.  Semaphores provide an elegant alternative solution to these problems in the
  14101.  context of the OS/2 multitasking environment. In one uninterruptible step a
  14102.  semaphore kernel call can test and set a semaphore. Thus, the semaphore
  14103.  controls access to a shared resource and allows one task to signal another
  14104.  that an event has occurred.
  14105.  
  14106.  To demonstrate a simple use of semaphores, suppose we added a facility to
  14107.  the keyboard thread program shown in Figure 8. This facility causes the
  14108.  keyboard thread to increment a counter every time the user presses the Esc
  14109.  key (instead of terminating the program). The main thread looks at the
  14110.  counter periodically, and as soon as the counter is greater than a certain
  14111.  value (say, 3), the main thread will terminate the program.
  14112.  
  14113.  The problem in OS/2's multithreaded environment concerns the serialization
  14114.  of a resource, specifically the counter variable that more than one thread
  14115.  may be sharing. Here's where the semaphore comes in: by using a semaphore,
  14116.  we can serialize the access to the counter variable, so that only one thread
  14117.  at a time actually reads or writes it.
  14118.  
  14119.  The revised listing, shown in Figure 13 (it uses the same MAKE file
  14120.  mentioned earlier and shown in Figure 10) illustrates the solution. It
  14121.  creates a semaphore variable, CountSem, which the main thread clears with
  14122.  the call to DosSemClear. The while loop has been expanded to handle the
  14123.  reading of the counter variable. The main thread sleeps for 100 milliseconds
  14124.  (about three 32-millisecond time slices), then calls DosSemRequest to gain
  14125.  access to CountSem. The -1L parameter causes the function to block the
  14126.  calling thread until the semaphore has been cleared--that way it will
  14127.  not be able to access the counter variable if the keyboard thread has
  14128.  already gained control.
  14129.  
  14130.  Next, the main thread evaluates the counter thread, breaks out of the loop,
  14131.  and terminates the program if the counter is greater than 3. Otherwise, it
  14132.  clears the semaphore (giving up ownership of the resource, the counter
  14133.  variable) and returns to the top of the loop. Note that the call to DosSleep
  14134.  suspends the current thread, allowing OS/2 to give CPU time to threads of
  14135.  the same or greater priority. Without the call to DosSleep, the thread would
  14136.  attempt to run in a continual loop, unnecessarily burning CPU time. Note,
  14137.  too, that the keyboard thread does not require DosSleep: the IO_WAIT
  14138.  parameter to KbdCharIn blocks that thread until there is keyboard input
  14139.  available.
  14140.  
  14141.  The keyboard thread code also uses the CountSem semaphore to gain access to
  14142.  the counter variable. Every time the user presses the Esc key, the keyboard
  14143.  thread requests access to the semaphore, blocking until the semaphore has
  14144.  been cleared. Then it increments the counter variable and clears the
  14145.  semaphore. Again, this mechanism prevents it from accessing the counter
  14146.  variable at the same time as the main thread. Thus, access to the counter
  14147.  variable has been serialized and the activity of the two threads has been
  14148.  synchronized. From this simple example, we can now move to something a
  14149.  little more complex: a multithreaded version of HELLO.C.
  14150.  
  14151.  Figure 10:
  14152.  
  14153.  #
  14154.  # make file for key.c example found in Figure 8
  14155.  #
  14156.  
  14157.  INCLUDE=\os2\include\mt
  14158.  LIB=\os2\lib
  14159.  COPT=/Lp /W3 /Zp /Zie /Zl /G2s /I$(INCLUDE)  /Alfw
  14160.  
  14161.  key.exe: key.c key
  14162.      cl $(COPT) key.c /link /co llibcmt
  14163.  
  14164.  
  14165.  A Multithreaded HELLO.C
  14166.  
  14167.  The multithreaded version of HELLO.C is designed to help illustrate the use
  14168.  of threads and semaphores for serializing access to and controlling
  14169.  resources. Whereas the original HELLO.C simply printed a message on the
  14170.  screen and terminated, HELLO0.C (shown in the listing in Figure 14),
  14171.  logically subdivides the screen into frames and prints the message in each
  14172.  frame. The program assigns each frame a thread that is responsible for
  14173.  writing and clearing the message. The program's threads continue to write
  14174.  and clear their messages until the user presses the Esc key. This
  14175.  frame-based format will be the basis for other example programs as we
  14176.  explore the OS/2 subsystems and other facilities in later articles in this
  14177.  series.
  14178.  
  14179.  Each frame's thread receives a pointer to the frame's data structure, which
  14180.  contains the information the thread will use during the course of the
  14181.  program. This structure includes the frame's row/column coordinates, the
  14182.  thread's ID (returned from _beginthread), a semaphore that the main program
  14183.  thread will use to activate the thread, and the thread's stack. Exactly how
  14184.  many frames will appear on the screen depends on the screen mode when you
  14185.  run the program (25, 43, or 50 lines).
  14186.  
  14187.  HELLO0.C can take one optional command-line argument: the number of
  14188.  milliseconds that the main thread should sleep between each activation of a
  14189.  frame thread. This argument allows you to slow down the visual activity
  14190.  deliberately so that you can see what's happening a little more clearly. The
  14191.  command-line parameter, stored in a variable called sleeptime, defaults to 1
  14192.  millisecond, which the DosSleep kernel function will round up to 32
  14193.  milliseconds--the minimum OS/2 time slice. It also prevents HELLO0.C
  14194.  from hogging too much CPU time. You might try running the program with a
  14195.  parameter of 50, 100, 500, or 1000 (the equivalent of 1 second) to get a
  14196.  better idea of what's happening.
  14197.  
  14198.  The program begins by checking the command-line parameter and then calls
  14199.  _beginthread to spin off a keyboard thread, which blocks until keyboard
  14200.  input is received and terminates the program when the user presses the Esc
  14201.  key. The code for this thread is lifted directly from the earlier keyboard
  14202.  thread example, shown in Figure 8. Next the main thread goes through several
  14203.  housekeeping and preparatory steps:  it obtains the video mode through a
  14204.  call to VioGetMode, sets up for the number of lines on the screen, and
  14205.  calculates the maximum number of frames to display. Then it allocates and
  14206.  initializes the frame structures (FRAME data types) and randomly chooses a
  14207.  FRAME for each screen frame, assigning the appropriate row/column
  14208.  coordinates. Consequently, the frames will seemingly appear and disappear in
  14209.  a random order, although the order remains static throughout the program.
  14210.  
  14211.  The real fun in HELLO0.C begins when the main thread calls DosSemSet for
  14212.  each FRAME, followed by a call to _beginthread to start the FRAME's thread
  14213.  (which will block until the semaphore clears). Finally, the main thread
  14214.  enters a loop where it stays for the remainder of the program. In this loop,
  14215.  it activates the thread for each FRAME by clearing the FRAME semaphore. The
  14216.  call to DosSleep suspends the thread, forcing it to give up some CPU time
  14217.  before activating the next thread. The main thread will do this for every
  14218.  FRAME and then repeat the process. Adding calls to DosSleep when a thread is
  14219.  in a loop will make the program more efficient, since it eliminates a
  14220.  thread's ability to waste CPU time.
  14221.  
  14222.  What does each FRAME thread do? The code for each FRAME thread is contained
  14223.  in the hello_thread function, which takes one parameter: the address of the
  14224.  thread's FRAME, which is passed to it by means   of _beginthread. The FRAME
  14225.  thread code is structured around a loop, at the top of which is a call to
  14226.  the DosSemRequest kernel function. By passing the address of a semaphore and
  14227.  a -1 to this function, the calling thread blocks until the semaphore
  14228.  clears. Thus, each thread is idle until the main thread clears its
  14229.  semaphore.
  14230.  
  14231.  Once activated, a FRAME thread will either clear or write its message,
  14232.  depending on a variable that is toggled every time the thread is active.
  14233.  Note that the VIO subsystem function, VioWrtCharStr, performs all video
  14234.  output and writes a specific number of characters from a string at a
  14235.  specific row/column coordinate on screen. An example of a FRAME thread's
  14236.  output is shown in Figure 15.
  14237.  
  14238.  As mentioned earlier, the program will continue until the user presses the
  14239.  Esc key. At that time, the keyboard thread will wake up (it's been blocked
  14240.  in the absence of keyboard input) and terminate the program.
  14241.  
  14242.  HELLO0.C is probably multithreaded overkill (how many times will you need to
  14243.  run as many as 25 threads in an application?), but it should get you off to
  14244.  a strong start and clarify the multithread issues addressed in the article.
  14245.  With this foundation, you'll be able to write some rather complex programs
  14246.  that use multiple threads. Indeed, program development will become even more
  14247.  interesting in the next issue, when we explore the VIO subsystem.
  14248.  
  14249.  Figure 11: Using DOSEXITLIST
  14250.  
  14251.  OS/2 lets a process establish a set of routines that will always be called
  14252.  when the process terminates.Typically these routines are functions that free
  14253.  the process's resources (such as closing open files). Regardless of how and
  14254.  when the process terminates, OS/2 will execute the functions upon
  14255.  termination. An application can "register" functions that OS/2 will execute,
  14256.  thus ensuring an orderly shutdown and disposal of the process's resources in
  14257.  spite of the unexpected termination of the process.
  14258.  
  14259.  The kernel function, DosExitList, registers the termination functions with
  14260.  OS/2, and terminates the functions themselves. The function prototype for
  14261.  DosExitList is:
  14262.  
  14263.  unsigned DosExitList(unsigned code,void far
  14264.      *fptr(unsigned));
  14265.  
  14266.  When registering the functions with OS/2, the first parameter can be either
  14267.  EXLST_ADD or EXLST_REMOVE, which add or remove a function from the list,
  14268.  respectively. By allowing a process to dynamically add and remove functions
  14269.  from the termination list, a process can control the destiny of its
  14270.  resources after its death (in a way, not unlike a human will). A process can
  14271.  remove the functions from the list prior to normal termination if they are
  14272.  no longer needed. The second parameter is, obviously, a pointer to the
  14273.  termination function being registered.
  14274.  
  14275.  When OS/2 begins execution of the termination functions, the process and all
  14276.  of its threads have been destroyed, with the exception of the thread
  14277.  executing the DosExitList functions. OS/2 will transfer control to each
  14278.  function registered, but in no particular order. Once OS/2 has executed all
  14279.  the registered functions, the process ends.
  14280.  
  14281.  The termination functions registered with DosExitList must be defined in the
  14282.  process (that is, in its code segment) and should be as short and fail-safe
  14283.  as possible. A termination function may call any OS/2 system function with
  14284.  the exception of DosCreateThread and DosExecPgm.
  14285.  
  14286.  A skeleton definition of a termination function follows:
  14287.  
  14288.  void far termfunc(unsigned code)
  14289.      {
  14290.      if(code != TC_EXIT)
  14291.          {
  14292.          .
  14293.          /* do cleanup here  */
  14294.          .
  14295.          }
  14296.      DosExitList(EXLST_EXIT,0);
  14297.      }
  14298.  
  14299.  Note that a termination function has one parameter and no return value. The
  14300.  parameter will always be one of the following:
  14301.  
  14302.  TC_EXIT    - normal exit
  14303.    TC_HARDERROR    - hard-error abort
  14304.    TC_TRAP    - trap operation
  14305.    TC_KILLPROCESS    - unintercepted DosKillProcess
  14306.  
  14307.  This allows the termination function to detect whether a normal termination
  14308.  of the process has occurred. It can also determine what actions the function
  14309.  should take.
  14310.  
  14311.  Termination functions must terminate themselves by calling
  14312.  DosExitList(EXLST_EXIT,0). They cannot execute a 'return' (explicitly or
  14313.  implicity, by falling past the curly brace), or the process will hang and
  14314.  never terminate. The EXLST_EXIT code tells OS/2 that the termination
  14315.  processing is complete, and that it should call the next function on the
  14316.  termination list.
  14317.  
  14318.  Figure 12:
  14319.  
  14320.  abs
  14321.  
  14322.  atoi
  14323.  
  14324.  atol
  14325.  
  14326.  bsearch
  14327.  
  14328.  chdir
  14329.  
  14330.  getpid
  14331.  
  14332.  halloc
  14333.  
  14334.  hfree
  14335.  
  14336.  itoa
  14337.  
  14338.  labs
  14339.  
  14340.  lfind
  14341.  
  14342.  lsearch
  14343.  
  14344.  memccpy
  14345.  
  14346.  memchr
  14347.  
  14348.  memcmp
  14349.  
  14350.  memcpy
  14351.  
  14352.  memicmp
  14353.  
  14354.  memmove
  14355.  
  14356.  memset
  14357.  
  14358.  mkdir
  14359.  
  14360.  movedata
  14361.  
  14362.  putch
  14363.  
  14364.  rmdir
  14365.  
  14366.  segread
  14367.  
  14368.  strcat
  14369.  
  14370.  strchr
  14371.  
  14372.  strcmp
  14373.  
  14374.  strcmpi
  14375.  
  14376.  strcpy
  14377.  
  14378.  stricmp
  14379.  
  14380.  strlen
  14381.  
  14382.  strlwr
  14383.  
  14384.  strncat
  14385.  
  14386.  strncmp
  14387.  
  14388.  strnicmp
  14389.  
  14390.  strncpy
  14391.  
  14392.  strnset
  14393.  
  14394.  strrchr
  14395.  
  14396.  strrev
  14397.  
  14398.  strset
  14399.  
  14400.  strstr
  14401.  
  14402.  strupr
  14403.  
  14404.  swab
  14405.  
  14406.  tolower
  14407.  
  14408.  toupper
  14409.  
  14410.  Figure 13:
  14411.  
  14412.  #define    INCL_DOS
  14413.  #define    INCL_SUB
  14414.  #include<stdio.h>
  14415.  #include<process.h>
  14416.  #include<os2.h>
  14417.  
  14418.  #define    ESC    0x1b
  14419.  #define    TRUE 1
  14420.  
  14421.  void keyboard_thread(void);
  14422.  void main(void);
  14423.  
  14424.  #define    THREADSTACK    512
  14425.  
  14426.  char keythreadstack[THREADSTACK];
  14427.  long CountSem =    0L;
  14428.  unsigned count = 0;
  14429.  
  14430.  void main(void)
  14431.      {
  14432.      TID    threadid;
  14433.  
  14434.      DosSemClear(&CountSem);
  14435.  
  14436.      if(DosCreateThread(keyboard_thread,&threadid,
  14437.              &keythreadstack[THREADSTACK-1]))
  14438.          exit(-1);
  14439.  
  14440.      while(TRUE)    /* insert code for main program here */
  14441.          {
  14442.          DosSleep(100L);
  14443.          DosSemRequest(&CountSem,-1L);
  14444.          if(count > 3)
  14445.              break;
  14446.          DosSemClear(&CountSem);
  14447.          }
  14448.      }
  14449.  
  14450.  void keyboard_thread(void)  /* keyboard thread code */
  14451.      {
  14452.      KBDKEYINFO keyinfo;
  14453.  
  14454.      while(TRUE)
  14455.          {
  14456.          KbdCharIn(&keyinfo,IO_WAIT,0);  /* wait for keystroke */
  14457.          if(keyinfo.chChar == ESC)     /* if ESC pressed, break */
  14458.              {
  14459.              DosSemRequest(&CountSem,-1L);
  14460.              count++;
  14461.              DosSemClear(&CountSem);
  14462.              }
  14463.          }
  14464.      DosExit(EXIT_PROCESS,0);          /* terminate the process */
  14465.      }
  14466.  
  14467.  Figure 14:
  14468.  
  14469.  /* os2hello.c by RHS, 10-14-88
  14470.   *
  14471.   * OS/2 and 1988 version of K&R's hello.c
  14472.   * demonstrates multiple threads
  14473.   */
  14474.  
  14475.  /*
  14476.  
  14477.  This program provides an introduction to the use of threads and semaphores
  14478.  under OS/2. It divides the screen up into a series of logical frames. Each
  14479.  frame is a portion of the screen that is managed (written to) by a single
  14480.  thread. The exact number of frames will depend on the current screen length
  14481.  (25, 43 and 50 lines). Each thread has its own data from which it knows
  14482.  where the frame can be found on screen. This includes a semaphore which
  14483.  signals the thread when to proceed. These elements can be found in the FRAME
  14484.  data type.
  14485.  
  14486.  Upon receiving a signal from its semaphore (i.e., the semaphore has been
  14487.  cleared), the thread either draws a message on the frame or clears the
  14488.  frame, and reverses the flag that determines this. Then it again blocks
  14489.  until its semaphore has been cleared again.
  14490.  
  14491.  The main program thread starts by setting up the frame information: checking
  14492.  the screen size, determining the number and size of the frames. It also
  14493.  "randomly" selects the order in which the frames will appear.
  14494.  
  14495.  Then it sets each thread's semaphore and initiates each thread (remember the
  14496.  threads will block until their semaphores are cleared.
  14497.  
  14498.  Finally, the main program goes into an infinite loop, clearing each thread's
  14499.  semaphore, sleeping for at least 1 millisecond, and then continuing to the
  14500.  next thread. Thus the threads asynchronously call the VIO subsystem to draw
  14501.  or clear each frame, while the main program thread continues.
  14502.  
  14503.  An optional parameter can be passed to set the number of milliseconds passed
  14504.  to DosSleep, allowing the operator to more accurately "see" the order in
  14505.  which the frames appear/erase. This value must always be at least 1 to allow
  14506.  the main program thread to give time to the CPU scheduler.
  14507.  
  14508.  A call to _beginthread() early in main() sets up a thread to monitor
  14509.  keyboard input. This thread blocks until a key is pressed, then examines the
  14510.  key, and if the key is the Escape Key (27 decimal or 1bH), the thread calls
  14511.  DosExit to kill the whole process.
  14512.  
  14513.  */
  14514.  
  14515.  #define  INCL_SUB
  14516.  #define  INCL_DOSPROCESS
  14517.  
  14518.  #include <os2.h>
  14519.  #include <mt\stdio.h>
  14520.  #include <mt\string.h>
  14521.  #include <mt\assert.h>
  14522.  #include <mt\stdlib.h>
  14523.  #include <mt\process.h>
  14524.  
  14525.  #if !defined(TRUE)
  14526.  #define TRUE    1
  14527.  #endif
  14528.  
  14529.  #if !defined(FALSE)
  14530.  #define FALSE   0
  14531.  #endif
  14532.  
  14533.  #define LINES25     4        /* height in lines of frames*/
  14534.  #define LINES43     6
  14535.  #define LINES50     7
  14536.  
  14537.  
  14538.  #define MAXFRAMES    28      /* limited to max frames possible */
  14539.  #define RAND()       (rand() % maxframes);
  14540.  #define THREADSTACK  400     /* size of stack each thread*/
  14541.  #define IDCOL        15
  14542.  #define ESC          0x1b
  14543.  
  14544.  char *blank_str = "                    "; /* string for
  14545.                                               blanking frame */
  14546.  
  14547.  
  14548.      /* frame data */
  14549.  
  14550.  char *hello_str25[LINES25+1] =
  14551.      {
  14552.      "                    ",
  14553.      "    Hello, world!   ",
  14554.      "  from Thread #     ",
  14555.      "                    ",
  14556.      "\0"
  14557.      };
  14558.  
  14559.  char *hello_str43[LINES43+1] =
  14560.      {
  14561.      "                    ",
  14562.      "                    ",
  14563.      "    Hello, world!   ",
  14564.      "  from Thread #     ",
  14565.      "                    ",
  14566.      "                    ",
  14567.      "\0"
  14568.      };
  14569.  
  14570.  char *hello_str50[LINES50+1] =
  14571.      {
  14572.      "                    ",
  14573.      "                    ",
  14574.      "    Hello, world!   ",
  14575.      "                    ",
  14576.      "  from Thread #     ",
  14577.      "                    ",
  14578.      "                    ",
  14579.      "\0"
  14580.      };
  14581.  
  14582.  
  14583.  char **helloptr;
  14584.  int numlines;
  14585.  
  14586.  typedef struct _frame   /* frame structure */
  14587.      {
  14588.      unsigned    frame_cleared;
  14589.      unsigned    row;
  14590.      unsigned    col;
  14591.      unsigned    threadid;
  14592.      long        startsem;
  14593.      char        threadstack[THREADSTACK];
  14594.      } FRAME;
  14595.  
  14596.  FRAME far *frames[MAXFRAMES];     /* pointers to frames */
  14597.  unsigned maxframes;
  14598.  
  14599.  ULONG sleeptime = 1L;             /* minim sleep time */
  14600.  char keythreadstack[THREADSTACK];
  14601.  
  14602.  
  14603.      /* function prototypes */
  14604.  
  14605.  void hello_thread(FRAME far *frameptr);
  14606.  void keyboard_thread(void);
  14607.  void main(int argc, char **argv);
  14608.  
  14609.  
  14610.  void main(int argc, char **argv)
  14611.      {
  14612.      int row, col, maxrows, maxcols, len, i, loops = 0;
  14613.      VIOMODEINFO viomodeinfo;
  14614.  
  14615.      if(argc > 1)
  14616.          sleeptime = atol(argv[1]);
  14617.      if(sleeptime < 1L)
  14618.          sleeptime = 1L;
  14619.  
  14620.      /* start keyboard thread */
  14621.      if(_beginthread(keyboard_thread,keythreadstack,
  14622.                      THREADSTACK,NULL) == -1)
  14623.          exit(-1);
  14624.  
  14625.      viomodeinfo.cb = sizeof(viomodeinfo);
  14626.      VioGetMode(&viomodeinfo,0);            /* get video info */
  14627.  
  14628.      maxrows = viomodeinfo.row;
  14629.      maxcols = viomodeinfo.col;
  14630.  
  14631.      switch(maxrows)
  14632.          {
  14633.          case 25:
  14634.              helloptr = hello_str25;
  14635.              numlines = LINES25;
  14636.              break;
  14637.          case 43:
  14638.              helloptr = hello_str43;
  14639.              numlines = LINES43;
  14640.              break;
  14641.          case 50:
  14642.              helloptr = hello_str50;
  14643.              numlines = LINES50;
  14644.              break;
  14645.          default:                 /* fail if not 25,43,50 lines */
  14646.              assert(0);
  14647.              exit(-1);
  14648.          }
  14649.  
  14650.      len = strlen(*helloptr);
  14651.  
  14652.      maxframes = (maxrows / numlines) * (maxcols / len);
  14653.  
  14654.      assert(maxframes <= MAXFRAMES);
  14655.  
  14656.      for( i = 0; i < maxframes; i++)   /* initialize structures */
  14657.          {
  14658.          if(!(frames[i] = malloc(sizeof(FRAME))))
  14659.              exit(0);
  14660.          frames[i]->frame_cleared = FALSE;
  14661.          frames[i]->startsem = 0L;
  14662.          memset(frames[i]->threadstack,0xff,
  14663.                 sizeof(frames[i]->threadstack));
  14664.          }
  14665.  
  14666.      i = RAND();     /* get first random frame */
  14667.  
  14668.  
  14669.      /* set up random appearance */
  14670.  
  14671.      for(row = col = 0; loops < maxframes ; )    /* set row/col
  14672.                                                     each frame */
  14673.          {
  14674.          if(!frames[i]->frame_cleared)
  14675.              {
  14676.              frames[i]->frame_cleared = TRUE; /* set for empty
  14677.                                                   frame */
  14678.              frames[i]->row = row;         /* frame upper row */
  14679.              frames[i]->col = col;         /* frame left column */
  14680.  
  14681.              col += len;                   /* next column on
  14682.                                               this row */
  14683.              if(col >= maxcols)            /* go to next row? */
  14684.                  {
  14685.                  col = 0;                  /* reset for start
  14686.                                               column */
  14687.                  row += numlines;          /* set for next row */
  14688.                  }
  14689.  
  14690.              i = RAND();                   /* get next random
  14691.                                               frame */
  14692.              }
  14693.          else
  14694.              ++i;
  14695.  
  14696.          if(i >= maxframes)
  14697.              {
  14698.              i -= maxframes;
  14699.              loops++;                       /* keep track of #
  14700.                                                of frames*/
  14701.              }
  14702.          }
  14703.  
  14704.  
  14705.      for( i = 0 ; i < maxframes; i++)       /* start a thread
  14706.                                                for each */
  14707.          {
  14708.          DosSemSet(&frames[i]->startsem);   /* initially set
  14709.                                                each sem. */
  14710.  
  14711.  
  14712.          /* start each thread */
  14713.  
  14714.          if((frames[i]->threadid = _beginthread(
  14715.                  (void far *)hello_thread,
  14716.                  (void far *)frames[i]->threadstack,
  14717.                  THREADSTACK,
  14718.                  (void far *)frames[i])) == -1)
  14719.              {
  14720.              maxframes = i;      /* reset maxframes on failure */
  14721.              break;
  14722.              }
  14723.          }
  14724.  
  14725.      while(TRUE)                 /* main loop */
  14726.          {
  14727.  
  14728.  
  14729.          /* swing thru frames, signalling to threads */
  14730.  
  14731.          for( i = 0; i < maxframes; i++)
  14732.              {
  14733.              DosSemClear(&frames[i]->startsem);   /* clear: thread
  14734.                                                      can go */
  14735.              DosSleep(sleeptime);             /* sleep a little */
  14736.              }
  14737.          }
  14738.      }
  14739.  
  14740.  
  14741.  void hello_thread(FRAME far *frameptr)    /* frame thread
  14742.                                               function */
  14743.  
  14744.      {
  14745.      register char **p;
  14746.      register int line;
  14747.      int len = strlen(*helloptr);
  14748.      unsigned row, col = frameptr->col;
  14749.      char idstr[20];
  14750.  
  14751.      while(TRUE)
  14752.          {
  14753.          DosSemRequest(&frameptr->startsem,-1L); /* block until
  14754.                                                     cleared */
  14755.          itoa(frameptr->threadid,idstr,10);      /* init idstr */
  14756.  
  14757.          row = frameptr->row;         /* reset row */
  14758.  
  14759.          if(!frameptr->frame_cleared) /* if frame in use, erase */
  14760.              for( line = 0; line < numlines; line++, row++)
  14761.                  VioWrtCharStr(blank_str,len,row,col,0);
  14762.          else                   /* else frame not in use */
  14763.              {
  14764.              p = helloptr;      /* print message */
  14765.              for( line = 0; **p; line++, row++, p++)
  14766.                  VioWrtCharStr(*p,len,row,col,0);
  14767.  
  14768.  
  14769.              /* write id # in frame */
  14770.  
  14771.                  VioWrtCharStr(idstr,3,
  14772.                                row-(numlines/2),
  14773.                                IDCOL+col,0);
  14774.              }
  14775.  
  14776.  
  14777.          /* toggle use flag */
  14778.  
  14779.          frameptr->frame_cleared = !frameptr->frame_cleared;
  14780.          }
  14781.      }
  14782.  
  14783.  void keyboard_thread(void)
  14784.      {
  14785.      KBDKEYINFO keyinfo;
  14786.  
  14787.      while(TRUE)
  14788.          {
  14789.          KbdCharIn(&keyinfo,IO_WAIT,0); /* wait for keystroke */
  14790.          if(keyinfo.chChar == ESC)      /* break if ESC pressed */
  14791.              break;
  14792.          }
  14793.      DosExit(EXIT_PROCESS,0);           /* terminate process */
  14794.      }
  14795.  
  14796.  
  14797.  /* end of hello0.c */
  14798.  
  14799.  Using The Multithreaded Library:
  14800.  
  14801.  DosCreateThread vs. _beginthread
  14802.  
  14803.  The OS/2 API interface for creating and terminating threads works through
  14804.  the kernel functions DosCreateThread and DosExit. Unfortunately, although
  14805.  the code for a thread must be contained within a function, you cannot pass
  14806.  parameters to it by using DosCreateThread. So if you prefer more of a C-like
  14807.  interface to write multithreaded programs, or you're going to use the
  14808.  multithreaded standard library, LLIBCMT.LIB, you should be familiar with an
  14809.  alternative interface via _beginthread and _endthread. If you plan on
  14810.  calling standard library functions from within your thread function, it's
  14811.  imperative that you use LLIBCMT.LIB.
  14812.  
  14813.  The Case of printf
  14814.  
  14815.  The discussion of printf in the main text illustrates the need for this
  14816.  interface. A function like printf uses a large internal buffer for
  14817.  formatting its output string. Although this buffer is adequate if a single
  14818.  thread is executing printf's code, the outcome is unpredictable if more than
  14819.  one thread is trying to execute printf at the same time. There are two ways
  14820.  that printf can be written to resolve this: you can use a semaphore to
  14821.  permit only one thread at a time to execute the code for printf, or you can
  14822.  use a set of semaphores to allow a finite number of threads to access printf
  14823.  simultaneously.
  14824.  
  14825.  Let's take a closer look at these two solutions. With the first method
  14826.  (sketched in Figure A), a semaphore is set at the beginning of printf and
  14827.  cleared at the end. This approach serializes the code for printf so that
  14828.  only one thread can execute it at a time. Unfortunately, that means that any
  14829.  time a thread calls printf, it will block if some other thread is executing
  14830.  the printf code and will remain suspended until the thread using printf
  14831.  clears the semaphore. Additionally, there is no guarantee that the next
  14832.  thread will be allowed to execute printf, nor can the scheduler ensure which
  14833.  thread that will be. OS/2 cannot guarantee that the next thread you want to
  14834.  call printf will be the one allowed to execute it with this approach. Thus,
  14835.  a scenario might develop where a thread of lesser priority might constantly
  14836.  be preempted by higher priority threads executing printf--and that can
  14837.  cause undesirable visual results.
  14838.  
  14839.  Alternatively, the second method limits the number of threads that can
  14840.  simultaneously execute the code for a function like printf. However, it
  14841.  offers no possibility of collision between threads competing for access to
  14842.  printf's code. With this approach (illustrated in Figure B), printf is
  14843.  structured to provide a fixed set of formatting buffers, with access to each
  14844.  controlled by a different semaphore. Consequently, every thread is given its
  14845.  own private buffer while executing the code. The catch is the limit on the
  14846.  number of buffers and therefore the limited number of threads that can gain
  14847.  access.
  14848.  
  14849.  This limit is one reason _beginthread is provided. The function offers more
  14850.  of a C-like approach to creating a new thread by allowing you to pass
  14851.  parameters to the thread and returning the thread ID when successful. But
  14852.  more specifically, it restricts the calling process to 32 threads, far less
  14853.  than the 255 threads that can be created by DosCreateThread. For many
  14854.  applications, however, 32 threads will be more than enough, allowing the
  14855.  multithreaded library, LLIBCMT.LIB, to operate on the assumption that no
  14856.  process will consist of more than 32 threads at a time. For this reason,
  14857.  _beginthread is available only when you use this library, and you should use
  14858.  it instead of DosCreateThread when writing a program that uses this library.
  14859.  
  14860.  Figure A:
  14861.  
  14862.  void printf(char *fmt,...)
  14863.      {
  14864.  static long printfSem = 0L;
  14865.  static char formatbuffer[BUFSIZ];
  14866.  
  14867.  DosSemRequest(&printfSem,-1L);
  14868.  
  14869.  ■
  14870.  ■
  14871.  ■
  14872.  
  14873.  DosSemClear(&printfSem);
  14874.  }
  14875.  
  14876.  Figure B:
  14877.  
  14878.  #define    MAXTHREADS    32
  14879.  void printf(char *fmt,...)
  14880.  {
  14881.  static long printfSems[MAXTHREADS] =
  14882.      {0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,
  14883.       0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,
  14884.       0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,
  14885.       0L,0L};
  14886.  
  14887.  char formatbuffers[MAXTHREADS][BUFSIZ];
  14888.  int semno;
  14889.  
  14890.  for(semno = 0; semno < MAXTHREADS; semno++)
  14891.      if(!DosSemRequest(&printfSems[semno],0L))
  14892.          break;
  14893.  assert(semno < MAXTHREADS);
  14894.  
  14895.  ■
  14896.  ■
  14897.  ■
  14898.  
  14899.  DosSemClear(&printfSem[semno]);
  14900.  
  14901.  }
  14902.  
  14903.  
  14904.  Using _beginthread
  14905.  
  14906.  How does _beginthread work? From the function prototype shown in Figure C,
  14907.  you can see that, like DosCreateThread, it requires the address of a
  14908.  function that contains the code for the thread. Unlike DosCreateThread,
  14909.  however, _beginthread takes not the address of the top of the stack, but the
  14910.  address of a stack area as you would declare it in your C program (that is,
  14911.  the bottom of the stack). Therefore you must provide it with the stack size
  14912.  (the third parameter). Last of all, _beginthread takes a parameter that
  14913.  makes it more valuable than DosCreateThread in some cases: an argument
  14914.  parameter for passing arguments to the thread function itself. An example of
  14915.  this use of _beginthread can be found in the multithreaded HELLO0.C program.
  14916.  Finally, you'll notice that _beginthread itself returns -1 on error or
  14917.  the thread ID of the new thread.
  14918.  
  14919.  Obviously, _beginthread must at some point call DosCreateThread. In fact, it
  14920.  even calls DosCreateThread when you have exceeded the number of threads
  14921.  allowed by the multithreaded library. The only way it can limit the number
  14922.  of threads to a process is to get the thread ID returned from its own call
  14923.  to DosCreateThread and return -1 if the ID is greater than 32.
  14924.  However, this reveals the use of two undocumented assumptions about OS/2:
  14925.  that DosCreateThread will always return the lowest available thread ID and
  14926.  that OS/2 will reuse thread IDs of previously terminated threads.
  14927.  
  14928.  Figure C:
  14929.  
  14930.  #include<mt\process.h>
  14931.  #include<mt\stddef.h>
  14932.  
  14933.  int cdecl far _beginthread(
  14934.            void (cdecl far *start_address) (void far *),
  14935.            void far *stack_end,
  14936.            unsigned stack_size,
  14937.            void far *arglist);
  14938.  
  14939.  ■
  14940.  ■
  14941.  ■
  14942.  void far cdecl _endthread(void)
  14943.  
  14944.  Linking LLIBCMT.LIB
  14945.  
  14946.  To use _beginthread and its counterpart to DosExit, _endthread (also shown
  14947.  in Figure C), make sure that the LLIBCMT.LIB and DOSCALLS.LIB libraries are
  14948.  available in the current directory or in the directory pointed to by the LIB
  14949.  variable (in the environment or in your MAKE file--see the MAKE file
  14950.  for HELLO0.C as an example). The DOSCALLS.LIB library is required, since
  14951.  _beginthread, _endthread, and some of the other library functions will make
  14952.  calls to OS/2 API functions. LLIBCMT.LIB should be used in place of any
  14953.  other run-time libraries.
  14954.  
  14955.  In addition, you may want to change your INCLUDE variable (again in the
  14956.  environment or in your MAKE file) to point to the MT directory that the
  14957.  compiler installed beneath the standard INCLUDE directory. This directory
  14958.  contains copies of the standard header files and should be used for creating
  14959.  multithreaded programs. You can set the INCLUDE variable on the compiler
  14960.  command line with the /I option as an alternative. Incidentally, the
  14961.  prototypes for _beginthread and _endthread can be found in PROCESS.H.
  14962.  
  14963.  Limitations
  14964.  
  14965.  Multithreaded code must make several assumptions as it executes. First, all
  14966.  code and data addresses are expected to be far. In addition, the code must
  14967.  assume that the data segment is fixed but should not assume that the stack
  14968.  and data segments are the same. In addition, conventional run-time stack
  14969.  checking must be turned off, since it is taken care of for each thread in a
  14970.  multithreaded program. You can conveniently use compiler switches to take
  14971.  care of these concerns by employing the /Alfw and /G2s options. Also, either
  14972.  the /Zl compiler option or the /NOD linker option should be used to prevent
  14973.  the default library search by the linker. The MAKE file for HELLO0.C
  14974.  illustrates this usage and can easily be adapted to compile and link your
  14975.  own multithreaded programs.
  14976.  
  14977.  Finally, remember that multithreaded programs cannot be bound into dual-mode
  14978.  applications since there is no facility in MS-DOS for simultaneous execution
  14979.  of multiple threads of code.
  14980.  
  14981.  ────────────────────────────────────────────────────────────────────────────
  14982.  
  14983.  Volume 4 - Number 3
  14984.  
  14985.  ────────────────────────────────────────────────────────────────────────────
  14986.  
  14987.  
  14988.  A Technical Study of Dynamic Data Exchange Under Presentation Manager
  14989.  
  14990.   Susan Franklin and Tony Peters
  14991.  
  14992.  The IBM Corporation and Microsoft recently shipped an updated version of the
  14993.  OS/2 operating system, which contains some important enhancements over the
  14994.  initial release of the OS/2 systems. The major enhancement to the OS/2
  14995.  Version 1.1 release is the inclusion of the Presentation Manager (referred
  14996.  to herein as PM) as a standard component. The OS/2 Presentation Manager is
  14997.  based on Microsoft(R) Windows and provides the same benefits Windows
  14998.  provided to DOS: a windowed, graphical user interface and support for a
  14999.  variety of input and output devices.
  15000.  
  15001.  An important component of Microsoft Windows that has been implemented in
  15002.  OS/2 PM is the Dynamic Data Exchange (DDE) protocol. The Windows DDE version
  15003.  was described in "Inter-Program Communication Using Windows' Dynamic Data
  15004.  Exchange," MSJ (Vol. 3, No. 6). DDE is a published message protocol for the
  15005.  exchange of data between participating programs and has gained wide
  15006.  acceptance among Windows applications as the standard messaging protocol for
  15007.  data exchange. The evolution of DDE from DOS to the OS/2 environment has
  15008.  required some enhancements to address limitations associated with the
  15009.  original Windows DDE specification. This article describes that evolution
  15010.  and provides a graphical data exchange program as an example.
  15011.  
  15012.  Protocol Modifications
  15013.  
  15014.  In the Windows environment, DDE provided a consistent, flexible method for
  15015.  communication between applications. When moving DDE to the multitasking,
  15016.  protected memory environment of the OS/2 PM, however, certain changes to the
  15017.  protocol were needed. These changes had to address the new concepts
  15018.  introduced by the OS/2 environment without significantly changing the DDE
  15019.  model, which has proved successful in the Windows environment.
  15020.  
  15021.  An early approach to the migration of the DDE protocol to OS/2 and
  15022.  Presentation Manager was a simple remapping of the message parameters. The
  15023.  primary change necessary was the parameter used when actually passing the
  15024.  data to another application, which requires crossing OS/2 process
  15025.  boundaries. Whereas a handle to the data is sufficient in the Windows
  15026.  environment, a memory selector is required to pass data between separate
  15027.  OS/2 processes. String data could still be passed in the global atom table,
  15028.  although applications were necessary to explicitly request access to the
  15029.  atom table. This approach solved the problems introduced by protected memory
  15030.  into the DDE message set. But several other problems and limitations still
  15031.  existed. They could be solved by further modification of the protocol.
  15032.  
  15033.  DDE suffered from the two-parameter limit inherent in PM messages. Following
  15034.  the Windows DDE model, the first parameter in any DDE message is the handle
  15035.  of the sending window. This left just one 32-bit parameter to pass all other
  15036.  conversation parameters and references to data. Although the necessary
  15037.  parameters and selectors could fit into the remaining long parameter, there
  15038.  was no room for any future expansion to the protocol.
  15039.  
  15040.  Communicating with other machines on a local area network (LAN) or with
  15041.  other types of computers proved difficult given the parameter limits. This
  15042.  limitation was also a problem if and when the operating system's addressable
  15043.  space increased. Even in the current environment, any expansion to the DDE
  15044.  model or conversation parameters would not be possible given the lack of
  15045.  parameter space. Clearly, the protocol had to be modified such that it
  15046.  permitted expandability and application freedom to include additional
  15047.  parameters as necessary.
  15048.  
  15049.  In order to expand the parameter space for DDE messages, the PM version of
  15050.  DDE uses the second DDE message parameter as a 32-bit pointer to one of two
  15051.  available DDE data structures. These structures contain all necessary DDE
  15052.  conversation parameters as well as the actual data when necessary. As the
  15053.  requirements for DDE change, this structure can be expanded without changing
  15054.  the parameters of the DDE messages. The long address of the structure
  15055.  permits compatibility to future system software and other machines. By
  15056.  packaging all parameters into one structure, the message parameters become
  15057.  more consistent since the variant parameters are contained in the structure
  15058.  itself.
  15059.  
  15060.  All parameters are packaged into a single structure, so the use of the atom
  15061.  table is no longer necessary. Adding string parameters to the atom table
  15062.  just introduces a second data access method that complicates the protocol.
  15063.  Instead string data is included in the DDE structures.
  15064.  
  15065.  Since the majority of DDE messages will be sent or posted to windows in a
  15066.  separate process, the memory containing the DDE structure had to be made
  15067.  accessible to the receiving window. Rather than require applications using
  15068.  DDE to grant this access, the PM DDE protocol provides new Application
  15069.  Program Interfaces (APIs) for sending and posting DDE messages. Applications
  15070.  do not use WinPostMsg or WinSendMsg to transmit DDE messages. Instead, the
  15071.  message and parameters are passed to a system API, which grants the
  15072.  receiving window access to the DDE structure and sends or posts the message
  15073.  on behalf of the calling application. These APIs ensure that the access to
  15074.  the structure is granted consistently and correctly, while simplifying the
  15075.  programming efforts of an application implementing DDE.
  15076.  
  15077.  These enhancements to DDE for OS/2 have provided a standard framework for
  15078.  applications to communicate without having to design a new protocol that
  15079.  would be suitable for DDE within a multitasking operating system.
  15080.  Additionally, using OS/2 DDE provides a concise method of implementing
  15081.  ever-increasingly complex graphical data exchange in an efficient manner.
  15082.  
  15083.  Single Client/Server
  15084.  
  15085.  In the simplest case, DDE is used when one application, called the client
  15086.  application, requires data from another independent application, called the
  15087.  server application. The classic example for this model is a charting program
  15088.  receiving updates of data from a spreadsheet and reflecting those changes by
  15089.  redrawing the chart. In that case, the charting program is the client and
  15090.  the spreadsheet is the server.
  15091.  
  15092.  We will begin by following the logic for the simplest of cases, the single
  15093.  client/single server model. In this example, the purpose of the DDE
  15094.  conversation is the exchange of graphical data between programs. The server
  15095.  application is any application wishing to display a picture in the client
  15096.  application's window. The graphical data is packaged and sent to the client
  15097.  application each time the server application wishes to change the appearance
  15098.  of its picture.
  15099.  
  15100.  Figure 1 shows the general logic flow for the single client/single server
  15101.  type of application. The client application initiates the conversation. Once
  15102.  the server acknowledges the initiate, the client requests the data and the
  15103.  server sends it inside the appropriate structure.
  15104.  
  15105.  A single client/single server DDE conversation begins when the client
  15106.  application broadcasts a WM_DDE_INITIATE message to all other top-level
  15107.  windows in the system. The client specifies the string name of the
  15108.  application expected to reply, as well as a string name identifying the
  15109.  topic of the proposed conversation. Either of these string names may be NULL
  15110.  to indicate that the desired server application or the topic name is not
  15111.  specific and any application implementing DDE may participate. The
  15112.  WM_DDE_INITIATE message is not broadcast directly by the application.
  15113.  Instead, the client application uses the WinDdeInitiate call to send the
  15114.  message. Figure 2 illustrates the procedure for initiation.
  15115.  
  15116.  When a server application gets the WM_DDE_INITIATE message, it checks the
  15117.  application and topic names to determine whether it will participate in the
  15118.  conversation. Sample code for initiate processing appears in Figure 3. Note
  15119.  that the application and string pointers that were input to the
  15120.  WinDdeInitiate call do not surface as explicit message parameters in the
  15121.  WM_DDE_INITIATE message. Instead, they are included in the DDEINIT
  15122.  structure, which is referenced by the second parameter. In all DDE messages,
  15123.  the first parameter contains the window handle of the window that originated
  15124.  the DDE message.
  15125.  
  15126.  If the server decides to participate in the conversation,  then the
  15127.  WinDdeRespond call   is used in order to send the WM_DDE_INITIATEACK message
  15128.  back to the client application. Again, the strings referenced in the
  15129.  parameters of this call will be copied to the DDEINIT structure, and the
  15130.  pointer to the structure will be passed as the second parameter in
  15131.  WM_DDE_INITIATEACK.
  15132.  
  15133.  Once the conversation link has been established, the actual data transfer
  15134.  may take place. This exchange may be a one-time data transfer, an ongoing
  15135.  data transfer, or a transfer of remote commands.
  15136.  
  15137.  One-time data transfer is accomplished by using the WM_DDE_REQUEST message
  15138.  when data is flowing from server to client and the WM_DDE_POKE message when
  15139.  data is flowing from client to server. In either case, a DDESTRUCT is
  15140.  allocated and filled by the client application. The structure contains
  15141.  status flags describing the format of the data that is being requested (or
  15142.  poked) as well as the status and the size of the data. Once   the structure
  15143.  is filled, the WM_DDE_REQUEST or WM_DDE_POKE message is posted to the server
  15144.  application using the WinDdePostMsg API. This is a call that is used for all
  15145.  messages   except   for WM_DDE_INITIATE and WM_DDE_INITIATEACK. The call
  15146.  ensures that the receiving window handle gets access to the memory allocated
  15147.  for the DDESTRUCT.
  15148.  
  15149.  When the data is being poked, the WM_DDE_POKE message is the only one
  15150.  required to complete the transfer, since the transfer is unsolicited. When
  15151.  the data is requested, the server application responds with the WM_DDE_DATA
  15152.  message. If the server cannot supply the data in the requested format, it
  15153.  responds with a negative WM_DDE_ACK message. Figure 4 illustrates the code
  15154.  required in the server application to handle both one-time data transfer
  15155.  methods.
  15156.  
  15157.  Perhaps the most common type of data transfer in DDE is the establishment of
  15158.  a permanent data link between applications. In that case, the client
  15159.  application posts a WM_DDE_ADVISE. The DDESTRUCT is filled in the same
  15160.  manner as it is for the WM_DDE_REQUEST message. This message informs the
  15161.  server application that the client would like to receive WM_DDE_DATA
  15162.  messages as the data change. As in the case of the WM_DDE_REQUEST message,
  15163.  the server responds with a negative WM_DDE_ACK message if the data cannot be
  15164.  supplied in the requested format. If the data can be supplied, the client
  15165.  will continue to receive WM_DDE_DATA messages until either the conversation
  15166.  terminates or the permanent link is terminated. Figure 5 shows the server
  15167.  handling a request for a permanent data link.
  15168.  
  15169.  When the client terminates the permanent link, it posts the WM_DDE_UNADVISE
  15170.  message. This message does not end the DDE conversation; rather, WM_DDE_DATA
  15171.  messages will no longer be posted when data changes.
  15172.  
  15173.  Another requirement during a DDE conversation is the execution of commands
  15174.  by a server application on behalf of the client. In this particular case,
  15175.  DDESTRUCT contains a string of commands to be executed. A positive or a
  15176.  negative WM_DDE_ACK message, depending on the outcome of the execution, is
  15177.  posted to the client. A code fragment for remote execution of commands is
  15178.  illustrated in Figure 6.
  15179.  
  15180.  Termination of a DDE conversation may be instigated by either the client or
  15181.  the server application. Typically, the WM_DDE_TERMINATE message is posted
  15182.  when the user has requested to close the application, although this isn't
  15183.  always the case. Whatever the reason for the termination, posting of the
  15184.  WM_DDE_TERMINATE indicates that no further DDE messages will be sent. The
  15185.  application that is posting the WM_DDE_TERMINATE may not shut down until the
  15186.  other application has responded with another WM_DDE_TERMINATE. There are no
  15187.  parameters used with the terminate message.
  15188.  
  15189.  Figure 2
  15190.  
  15191.  case WM_CREATE:    /* broadcast the initiate message  */
  15192.     WinDdeInitiate(hwnd, "App_Name", "Graphics_Exchange");
  15193.  
  15194.  break;
  15195.  
  15196.  Figure 3:
  15197.  
  15198.  case WM_DDE_INITIATE:  /* respond if app and topic strings match */
  15199.   pDDEInit = (PDDEINIT)lParam2;
  15200.   if ((HWND)lParam1 != hwnd) {
  15201.      if((!strcmp("App_Name", pDDEInit->pszAppName)) &&
  15202.          (!strcmp("Graphics_Exchange", pDDEInit->pszTopic))) {
  15203.             WinDdeRespond(lParam1, hwnd, "Client",
  15204.             "Graphics_Exchange");
  15205.      }
  15206.   }
  15207.   DosFreeSeg(PDDEITOSEL(pDDEInit));
  15208.  break;
  15209.  
  15210.  Figure 4:
  15211.  
  15212.  case WM_DDE_REQUEST: /* allocate DDESTRUCT and send data */
  15213.   pDDEStruct = (PDDESTRUCT)lParam2;
  15214.   strcpy(szTemp, "Text_Data");
  15215.   if((pDDEStruct->usFormat == DDEFMT_TEXT) &&
  15216.       (!strcmp(szTemp, DDES_PSZITEMNAME(pDDEStruct)))) {
  15217.           nNumBytes = (strlen(szTemp) + 2 + strlen(szData));
  15218.           pDDEStruct = st_DDE_Alloc((sizeof(DDESTRUCT) + nNumBytes),
  15219.                                     "DDEFMT_TEXT");
  15220.           pDDEStruct->cbData = strlen(szData) + 1;
  15221.           pDDEStruct->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15222.           pDDEStruct->offabData = (USHORT)((sizeof(DDESTRUCT) +
  15223.                                         strlen(szTemp)) + 1);
  15224.           memcpy(DDES_PSZITEMNAME(pDDEStruct), szTemp, (strlen(szTemp)
  15225.                  + 1));
  15226.           memcpy(DDES_PABDATA(pDDEStruct), szData, (strlen(szData)
  15227.                  + 1));
  15228.           pDDEStruct->fsStatus |= DDE_FRESPONSE;
  15229.  
  15230.  WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_DATA, pDDEStruct,
  15231.  
  15232.  TRUE);
  15233.           DosFreeSeg(PDDESTOSEL(lParam2));
  15234.   }
  15235.   else {  /* send negative ACK using their DDESTRUCT */
  15236.        pDDEStruct->fsStatus &= (~DDE_FACK);
  15237.        WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_ACK, pDDEStruct,
  15238.                      TRUE);
  15239.   }
  15240.  break;
  15241.  
  15242.  case WM_DDE_POKE: /* unsolicited data from client */
  15243.     pDDEStruct = (PDDESTRUCT)lParam2;
  15244.     strcpy(szTemp, "Text_Data");
  15245.     if((pDDEStruct->usFormat == DDEFMT_TEXT) &&
  15246.        (!strcmp(szTemp, DDES_PSZITEMNAME(pDDEStruct)))) {
  15247.             strcpy(szData, DDES_PABDATA(pDDEStruct));
  15248.             DosFreeSeg(PDDESTOSEL(lParam2));
  15249.   }
  15250.   else {  /* send negative ACK using their DDESTRUCT */
  15251.        pDDEStruct->fsStatus &= (~DDE_FACK);
  15252.        WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_ACK, pDDEStruct,
  15253.                      TRUE);
  15254.   }
  15255.  
  15256.  break;
  15257.  
  15258.  Figure 5:
  15259.  
  15260.  case WM_DDE_ADVISE:  /* set ADVISE bit in window data and ACK */
  15261.   pDDEStruct = (PDDESTRUCT)lParam2;
  15262.   if(pDDEStruct->usFormat == DDEFMT_TEXT) {
  15263.      pWWVar->bAdvise = TRUE;
  15264.      if((pDDEStruct->fsStatus & DDE_FACKREQ) == DDE_FACKREQ) {
  15265.         pDDEStruct->fsStatus |= DDE_FACK;
  15266.         WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_ACK, pDDEStruct,
  15267.                       TRUE);
  15268.      }
  15269.      else {
  15270.          DosFreeSeg(PDDESTOSEL(lParam2));
  15271.      }
  15272.   else {    /* Send a negative ACK using their DDESTRUCT */
  15273.      pDDEStruct->fsStatus &= (~DDE_FACK);
  15274.  
  15275.  WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_ACK, pDDEStruct, TRUE);
  15276.  
  15277.  }
  15278.  break;
  15279.  
  15280.  Figure 6:
  15281.  
  15282.  case WM_DDE_EXECUTE:                   /* execute a command */
  15283.     pDDEStruct = (PDDESTRUCT)lParam2;
  15284.     strcpy(szCommand, DDES_PABDATA(pDDEStruct);
  15285.     if(!Dde_Cmd_Processor(szCommand)) {   /* parse and execute
  15286.                                              the command */
  15287.            pDDEStruct->fsStatus &= (~DDE_FACK);
  15288.            WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_ACK,
  15289.                          pDDEStruct, TRUE);
  15290.   }
  15291.   else {
  15292.      DosFreeSeg(PDDESTOSEL(lParam2));
  15293.   }
  15294.  
  15295.  break;
  15296.  
  15297.  
  15298.  
  15299.  User-Defined Formats
  15300.  
  15301.  It is possible for an application to create its own DDE data format if an
  15302.  appropriate, system-provided data format is not available. The system
  15303.  provides one predefined format for interchanging text strings, DDEFMT_TEXT.
  15304.  Before an application uses an application-defined data format, however, it
  15305.  must establish a convention for obtaining a unique ID and registering that
  15306.  format so other applications can associate the format ID in the DDESTRUCT
  15307.  with their specialized data format. Although appearing similar to clipboard
  15308.  data formats, DDE formats are not to be confused with clipboard formats.
  15309.  Clipboard formats identify handle types, whereas DDE formats identify the
  15310.  actual layout of the data in the DDESTRUCT block. We have chosen to register
  15311.  DDE formats using the system atom table. The prefix of DDE formats is
  15312.  DDEFMT_. Using the atom manager to register DDE formats guarantees unique
  15313.  IDs among all applications that use this method to register DDE formats.
  15314.  
  15315.  Figure 7 represents a function, Register_DDEFMT, which illustrates how to
  15316.  register a user-defined DDE data format with the system. Register_DDEFMT
  15317.  returns the DDE data format for either an existing or a newly created data
  15318.  format. The first time Register_DDEFMT is invoked for a particular DDE data
  15319.  format, that format is registered in the system atom table. Every call that
  15320.  is sent to Register_DDEFMT for a particular DDE format string identifier
  15321.  results in the format being retrieved and returned to the caller. Looking
  15322.  ahead, Figure 9 shows an example of a function call to Register_DDEFMT.
  15323.  
  15324.  The user DDE data format for the DDE graphics exchange sample program in
  15325.  this article is shown in Figure 8. The format consists of a graphics
  15326.  descriptor control block in the abData area. This graphics descriptor
  15327.  control block contains, among other information, an offset pointer to the
  15328.  graphics data that is passed in the same shared memory block following the
  15329.  GDE data in abData.
  15330.  
  15331.  Figure 7:
  15332.  
  15333.  USHORT Register_DDEFMT(pszFormat)
  15334.  PSZ pszFormat;
  15335.  {
  15336.  HATOMTBL hAtomtbl;
  15337.  USHORT   retn;
  15338.  
  15339.      hAtomtbl = WinQuerySystemAtomTable();
  15340.      if (retn = WinFindAtom(hAtomtbl,pszFormat))
  15341.         return retn;
  15342.      else
  15343.         return (WinAddAtom(hAtomtbl,pszFormat));
  15344.  
  15345.  }
  15346.  
  15347.  Using Shared Memory
  15348.  
  15349.  DDE uses shared memory for all communications. Two different types of memory
  15350.  objects are allocated for DDE transactions--the DDEINIT and the
  15351.  DDESTRUCT structures. The way these objects are allocated may differ, but
  15352.  both result in shared memory made available to the recipient. These shared
  15353.  memory objects need to be freed properly for OS/2 shared memory management
  15354.  to be effective. The data areas containing szAppName and szTopicName in the
  15355.  WinDdeInitiate and WinDdeRespond calls may be in private or shared memory.
  15356.  When the WinDdeInitiate and WinDdeRespond calls are executed, the system
  15357.  will copy those strings into DDEINIT and make DDEINIT available to the
  15358.  recipient.
  15359.  
  15360.  The data area that contains   the DDESTRUCT structure must be allocated
  15361.  using the SEG_GIVEABLE flag in the DosAllocSeg and DosAllocHuge calls. The
  15362.  WinDdePostMsg API makes the DDESTRUCT memory object available to the message
  15363.  recipient and frees the object from the sender.
  15364.  
  15365.  Any pointers that are part of the abData field must point to shared memory.
  15366.  It is the application's responsibility to manage the allocation and sharing
  15367.  synchronization of these shared data areas. The application may use either
  15368.  base OS/2 memory management API calls or higher level memory subsetting
  15369.  common services to achieve this, as long as the memory is managed properly.
  15370.  
  15371.  The message recipient is responsible for freeing all memory objects after
  15372.  retrieving the information. DosFreeSeg is used to release the memory.
  15373.  DDEINIT and DDESTRUCT must both be released by the message recipient.
  15374.  
  15375.  Figures 9 and 10 show the allocation and deallocation of the shared memory
  15376.  objects necessary for processing of the WinDdePostMsg API. The first part of
  15377.  Figure 9 is a call to a local function, which allocates and initializes the
  15378.  DDE shared memory object. The accompanying function, DDE_Alloc(), does the
  15379.  actual shared memory allocation by making a call to DosAllocSeg() and clears
  15380.  the entire shared memory object. The DDE shared memory block is initialized
  15381.  with the user-defined data DDE format by setting usFormat field in
  15382.  DDESTRUCT. This is accomplished by calling the function Register_DDEFMT(),
  15383.  which we previously discussed. By allocating and initializing the DDE shared
  15384.  memory objects in this manner, we can readily obtain ready-to-use shared
  15385.  memory objects for each of our DDE transactions.
  15386.  
  15387.  Figure 10 shows how deallocation of the DDESTRUCT, in this case during the
  15388.  processing of a WM_DDE_DATA message, is accomplished by calling DosFreeSeg
  15389.  with the selector of the DDE memory object as its parameter.
  15390.  
  15391.  Figure 9:
  15392.  
  15393.  /* send a request for data */
  15394.  
  15395.         /* allocate memory  */
  15396.         DDEstrptr = DDE_Alloc(sizeof(DDESTRUCT), IDS_GDE);
  15397.  
  15398.  WinDdePostMsg((HWND)lParam1, DDEtoHWND, (ULONG)WM_DDE_REQUEST,
  15399.  
  15400.  DDEstrptr, TRUE);
  15401.  
  15402.  .
  15403.  .
  15404.  .
  15405.  
  15406.  PDDESTRUCT DDE_Alloc(size, format)
  15407.  int size;
  15408.  char *format;
  15409.  
  15410.  /************************************************************
  15411.   *  1. Allocate a block of size. bytes
  15412.   *     of giveable, shared memory for DDE call.
  15413.   *  2. Fill in DDE data format by
  15414.   *     calling Register_DDEFMT((PSZ)format);.
  15415.   ************************************************************/
  15416.  {
  15417.  SEL     ddepsel;
  15418.  USHORT  dasret;
  15419.  PDDESTRUCT DDEstrptr;
  15420.     if ((dasret = DosAllocSeg(size, &ddepsel, SEG_GIVEABLE)) == 0) {
  15421.         DDEstrptr = (PDDESTRUCT)SELTOPDDES(ddepsel);
  15422.         memset(DDEstrptr, (BYTE)NULL, size); /*  set allocated memory
  15423.                                                  to nulls  */
  15424.         /* fill in DDE data format */
  15425.         DDEstrptr->usFormat = Register_DDEFMT((PSZ)format);
  15426.  
  15427.     } else {   /*  error  */
  15428.         return((PDDESTRUCT)NULL);
  15429.       }
  15430.  
  15431.  }
  15432.  
  15433.  Figure 10:
  15434.  
  15435.  MRESULT APIENTRY MasterDDEWndProc(hwnd,message,lParam1,lParam2)
  15436.  
  15437.  ■
  15438.  ■
  15439.  ■
  15440.  
  15441.      DDEstrptr = (PDDESTRUCT)lParam2;
  15442.  
  15443.      case WM_DDE_DATA:
  15444.  
  15445.  ■
  15446.  ■
  15447.  ■
  15448.  
  15449.  DosFreeSeg(PDDESTOSEL(DDEstrptr));
  15450.  
  15451.  DDEINIT and DDESTRUCT
  15452.  
  15453.  Presentation Manager processes the data and allocates the memory for the
  15454.  WinDdeInitiate and WinDdeRespond API calls as shown in Figure 11. The system
  15455.  takes the strings passed in the WinDdeInitiate call and copies the strings
  15456.  into multiple DDEINIT blocks that are broadcast to all applications running
  15457.  on the system. Participating DDE server applications process the
  15458.  WinDdeInitiate call by sending a WM_DDE_INITIATEACK message with the
  15459.  WinDdeRespond call to the client and freeing the DDEINIT memory object from
  15460.  the WinDdeInitiate call. Non-DDE applications don't respond to the
  15461.  WM_DDE_INITIATE message and the shared memory object is released by the
  15462.  system default winproc.
  15463.  
  15464.  Figure 11 also illustrates how the DDESTRUCT is allocated, passed, and
  15465.  released by the client and server applications. Figure 12 is a diagrammatic
  15466.  view of DDEINIT processing.
  15467.  
  15468.  Multiclient/Server Model
  15469.  
  15470.  Earlier we discussed the single client/single server DDE conversation model.
  15471.  It may be possible, and in a multitasking environment such as OS/2 PM it is
  15472.  likely, that 1) server applications may have to support multiple clients and
  15473.  2) multiple clients can receive data simultaneously from multiple servers.
  15474.  Likewise, there may be little or no distinction between whether the client
  15475.  or server application is initiated first. In a multiclient/multiserver
  15476.  application relationship, multiple clients and server applications can be
  15477.  invoked in any conceivable order, with virtually any number of permutations.
  15478.  The real power of DDE to manage conversations efficiently becomes apparent
  15479.  in implementing multiclient/multiserver relationships.
  15480.  
  15481.  In managing an N-way conversation, the client, the server, or both
  15482.  applications may be involved in a one-to-many conversation. That is, a
  15483.  single server may be supplying data to multiple client applications, a
  15484.  client application may be receiving data from several servers, or both of
  15485.  these things may be happening.
  15486.  
  15487.  The DDE convention for managing one-to-many conversations is for the
  15488.  managing application to open a window--the conversational DDE
  15489.  window--for each conversation and to process the messages in a generic
  15490.  winproc for each conversation based on the conversational DDE window handle.
  15491.  There are several benefits associated with managing the conversations based
  15492.  on a window handle assigned to the conversation.
  15493.  
  15494.  First, the individual conversation window handles serve as an ID for the
  15495.  conversation, which is guaranteed by the operating system to be unique.
  15496.  Second, conversations can easily be maintained and manipulated by using PM
  15497.  API calls (for example, WinEnumerateWindow) without having to develop
  15498.  specific data structures, such as linked lists, to keep track of
  15499.  conversations. Third, it is possible to easily store and retrieve
  15500.  conversational specific data in window words created with each
  15501.  conversational DDE window. We have created one additional window in each DDE
  15502.  application to facilitate DDE processing by our applications.
  15503.  
  15504.  Each of our DDE applications creates a DDE anchor  window, which imposes an
  15505.  artificial one-layer window hierarchy on the application window structure,
  15506.  isolating all the DDE windows under a single parent window. This lets us
  15507.  search through the DDE conversations directly using WinEnumerateWindow
  15508.  without having to search all application children windows for DDE
  15509.  conversations. The DDE conversation windows are normally traversed to locate
  15510.  information specific to a single conversation or during termination
  15511.  processing when all the links of a particular application are being
  15512.  terminated. Figure 13 illustrates the parent/child relationship of DDE
  15513.  windows in our applications.
  15514.  
  15515.  When either the server or the client can initiate the conversation, as can
  15516.  be done in a multiclient/multiserver application, a client/server
  15517.  relationship that is consistent with the single client/single server DDE
  15518.  conversation model should be maintained. An example of this situation is a
  15519.  newly invoked server application participating in an existing client/server
  15520.  conversation.
  15521.  
  15522.  When a server is invoked during execution of a client, the server must
  15523.  signal the client that it is willing to participate in a conversation. For
  15524.  our signal, we have chosen to send the WM_DDE_INITIATE message with a
  15525.  predefined application name. The client responds to the server's initiate
  15526.  message with an initiate message of its own, causing the server to respond
  15527.  with a WinDdeRespond message, as would be done under the single
  15528.  client/single server model.
  15529.  
  15530.  Graphics Exchange Program
  15531.  
  15532.  DDE extensions for the multiclient/multiserver model are implemented in the
  15533.  sample graphics exchange program. For sample purposes, the client
  15534.  application exists merely to display graphical pictures representing the
  15535.  running server applications. Server applications may differ greatly in the
  15536.  function provided, but they all use DDE to transfer their graphics data to
  15537.  the client. In our model, the client always establishes a permanent data
  15538.  link with the server to receive updates as the state of the server
  15539.  application warrants a change in the picture's appearance. We have chosen
  15540.  Graphics_Exchange as the topic name  for this example.
  15541.  
  15542.  The server provided in the example is simulating a phone messaging service.
  15543.  As phone messages arrive, they are listed in the main window of the server
  15544.  application. Each time a message is added, the picture is updated so that it
  15545.  reflects the current number of phone messages, and the client is posted a
  15546.  WM_DDE_DATA message. In order to limit the complexity of the server code
  15547.  presented, phone messages are generated through the use of the timer. At
  15548.  regular intervals, phone messages are added or deleted from the list. This
  15549.  lets us focus our attention on the DDE implementation in the program rather
  15550.  than on the function behind the server application.
  15551.  
  15552.  In Figure 14 the server application has been invoked and is generating phone
  15553.  messages. When the client application is invoked, it broadcasts the
  15554.  WM_DDE_INITIATE message and the server responds. Note that the application
  15555.  string is zero length to indicate that the client will talk to any
  15556.  application that will respond to the topic of Graphics_Exchange. Figure 15
  15557.  shows the screen after the conversation has been linked and the first
  15558.  picture has been transferred to the client application.
  15559.  
  15560.  Upon invocation of a second instance of the server application, the server
  15561.  must signal the client that it has begun execution. In our case, the server
  15562.  broadcasts the WM_DDE_INITIATE message using the same topic but specifying a
  15563.  target application of Client. This indicates that the initiate is a special
  15564.  case in which the server is signaling its invocation to any client
  15565.  applications that may want to subsequently initiate a conversation. Upon
  15566.  receiving this message, the client application broadcasts another
  15567.  WM_DDE_INITIATE message. If the application string name were zero length,
  15568.  however, the client would inadvertently establish a second link with the
  15569.  original server application. To prevent this occurrence, the client
  15570.  specifies an application name, which is simply the handle of the new server
  15571.  converted to a string name. The newly invoked server checks the application
  15572.  name and responds because the application name matches its handle. Figure 16
  15573.  illustrates the message flow in establishing the second link.
  15574.  
  15575.  This signaling convention establishes several rules for initiation of a
  15576.  Graphics_Exchange conversation and the subsequent response. The client
  15577.  application must broadcast a   WM_DDE_INITIATE upon invocation with the null
  15578.  application string. It must also be prepared to receive a WM_DDE_INITIATE
  15579.  message as a signal of a newly invoked server. If the application name
  15580.  Client is present in this message, then the client must again broadcast a
  15581.  WM_DDE_INITIATE, this time with an application string containing the handle
  15582.  of the new participant. Figure 17 contains the initiate processing for the
  15583.  Graphics_Exchange client application.
  15584.  
  15585.  The server must respond to a WM_DDE_INITIATE of topic Graphics_Exchange if
  15586.  and only if the application string name is zero  length or if it contains
  15587.  the string representation of the server's window handle. Any other
  15588.  application string name should be ignored. If the server responds to the
  15589.  conversation, it creates a window to handle all future processing of the new
  15590.  link and responds to the client using this window handle. Note that a window
  15591.  word containing the conversation link count is incremented at this time.
  15592.  This counter will be used later by the server to determine when shutdown may
  15593.  occur. Figure 18 shows the response processing in the Graphics_Exchange
  15594.  server application.
  15595.  
  15596.  Upon receipt of the WM_DDE_INITIATEACK message, the client application
  15597.  creates a window to process the link. Just as the server did, the client
  15598.  increments a conversation link count stored in its window word. This will be
  15599.  used during TERMINATE processing. The WM_DDE_REQUEST and WM_DDE_ADVISE
  15600.  messages are posted to the server on behalf of the newly created
  15601.  conversation window. Figure 19  illustrates this processing.
  15602.  
  15603.  The primary activity of the server window is the packaging of the data and
  15604.  posting of the WM_DDE_DATA message. In the sample program, this activity
  15605.  always occurs during WM_DDE_REQUEST processing. That is, even when the main
  15606.  server application determines that data should be transferred as a result of
  15607.  an ongoing ADVISE, the result is the posting of a WM_DDE_REQUEST by the main
  15608.  application to all of the conversation windows on behalf of the applications
  15609.  being advised. The format of the data message is a user-defined data format
  15610.  that was registered as discussed previously.
  15611.  
  15612.  This data format is actually a data structure, GDEDATA, which is used as the
  15613.  input structure for a control that manipulates the graphics for the client
  15614.  application. Upon receiving a WM_DDE_REQUEST, the server allocates memory
  15615.  for the DDESTRUCT and the underlying data structure and fills in the
  15616.  necessary data fields required by the client and its control. The actual
  15617.  graphics may be deposited in either bitmap or GPI drawing orders format and
  15618.  included in the memory object. In the sample, they are always deposited as
  15619.  drawing orders. The whole data package is then transmitted via the
  15620.  WinDdePostMsg call.  The complete processing of the WM_DDE_REQUEST message
  15621.  is shown in Figure 20.
  15622.  
  15623.  Figure 21 shows how the main server application generates WM_DDE_REQUEST
  15624.  messages on behalf of all advised clients when the data changes. This is
  15625.  done by enumerating all child windows of the anchor window handle and
  15626.  posting the message to those windows that are advising a client application.
  15627.  The advise status of a server application is stored in the window word of
  15628.  the conversation window procedure. Figure 22 illustrates the setting of this
  15629.  status field upon receiving either the WM_DDE_ADVISE or the WM_DDE_UNADVISE
  15630.  message.
  15631.  
  15632.  The data format for a Graphics_Exchange conversation is simply the input
  15633.  structure to a control, therefore the WM_DDE_DATA processing by the client
  15634.  is no more than an insertion or replacement of the data structure into the
  15635.  control. If the DDE_FRESPONSE bit is set, the client knows that the
  15636.  WM_DDE_DATA message is the result of the initial WM_DDE_REQUEST that was
  15637.  made after the conversation was linked. Since this is the first transmittal
  15638.  of data from the server, the picture must be inserted into the graphics
  15639.  control. If the DDE_FRESPONSE bit is not set, then the WM_DDE_DATA message
  15640.  is the result of an ongoing advise and the client must replace the picture
  15641.  in the control with the new data.
  15642.  
  15643.  The control messages themselves are quite simple. The first parameter
  15644.  identifies the ID of the picture in question, and the second points to the
  15645.  structure describing and containing the picture. The internal details of the
  15646.  control are not relevant; the control manages the appropriate sizing and
  15647.  placement of the graphics data. The complete WM_DDE_DATA processing is shown
  15648.  in Figure 23.
  15649.  
  15650.  During execution of the participating applications, the WM_DDE_DATA messages
  15651.  will be sent each time the timer triggers the delivery or removal of a phone
  15652.  message. The DDE conversation will continue until either application
  15653.  terminates the conversation. In our example, the conversation is only
  15654.  terminated when the user attempts to close either application. The WM_CLOSE
  15655.  processing as well as the subsequent WM_DDE_TERMINATE processing follow the
  15656.  same algorithm in both the client and server applications.
  15657.  
  15658.  When a WM_CLOSE message is received, the application enumerates all DDE
  15659.  conversation windows (by enumerating on the anchor window). For each
  15660.  conversation window, a window word is set to indicate that the user has
  15661.  requested a shutdown of the application. At the same time, another window
  15662.  word is queried to determine the handle of the conversation window with
  15663.  which the enumerated window is exchanging data. That window is posted a
  15664.  WM_DDE_TERMINATE message by the main application on behalf of the
  15665.  conversation window. No further shutdown processing will take place until
  15666.  all conversation links have terminated. Figure 24 shows the WM_CLOSE
  15667.  processing by the client application. Note that the server code is very
  15668.  similar.
  15669.  
  15670.  When it receives the WM_DDE_TERMINATE message, a conversation window checks
  15671.  its window word to determine if the close was initiated by its own
  15672.  application or by its conversation partner. If the close was initiated by
  15673.  the conversation partner, the window posts a corresponding WM_DDE_TERMINATE
  15674.  to the sender. If the close was not initiated by the conversation partner,
  15675.  the message is simply an acknowledgment by the partner that terminate
  15676.  processing may continue. In either case, the conversation window may now
  15677.  destroy itself, since its conversation link is ending. At this time, it
  15678.  posts an application-defined message, called APPM_CONV_CLOSE, to indicate to
  15679.  the main window that the link has successfully terminated. Figure 25 shows
  15680.  the WM_DDE_TERMINATE processing by the client. Again, the algorithm is the
  15681.  same in the server application.
  15682.  
  15683.  The APPM_CONV_CLOSE message serves as the signal that final shutdown may
  15684.  occur. As each APPM_CONV_CLOSE message is received, the link counter is
  15685.  decremented. When the link counter reaches zero, the main application checks
  15686.  its window word to determine whether a close was requested for the
  15687.  application. If close was requested and all links have successfully
  15688.  terminated, the application may complete its shutdown and terminate (see
  15689.  Figure 26).
  15690.  
  15691.  Graphics exchange provides an extremely visual working example of the use of
  15692.  DDE to establish permanent links between separate PM applications. Support
  15693.  for multiple conversation management is added simply by defining one
  15694.  additional message and making extensive use of the window word and window
  15695.  enumeration facilities. Of course, these applications may be expanded to
  15696.  exchange other data in addition to the graphics representations exchanged in
  15697.  this program.
  15698.  
  15699.  By using our example as a guide, you should be able to generalize the
  15700.  implementation presented in order to develop your own multitasking
  15701.  interprogram data exchange. We have found DDE to be a powerful and flexible
  15702.  element of Presentation Manager, providing an added dimension to developing
  15703.  interacting applications in the OS/2 multitasking environment. As the
  15704.  programming environment has evolved from Windows to the extensive
  15705.  capabilities of the OS/2 Presentation Manager, the changes made to the DDE
  15706.  protocol have kept pace with this new function. Moreover, it has provided a
  15707.  framework for further expansion as the environment continues to evolve.
  15708.  
  15709.  Figure 17
  15710.  
  15711.  case WM_CREATE:  /* broadcast the initiate message  */
  15712.       WinDdeInitiate(hwnd, "", "Graphics_Exchange");
  15713.       break;
  15714.  
  15715.  case WM_DDE_INITIATE: /* reply with an initiate to specific server */
  15716.     if (hwnd != lParam1) {
  15717.         if ((!strcmp("Client", DDEInitPtr->pszAppName)) &&
  15718.             (!strcmp("Graphics_Exchange",DDEInitPtr->pszTopic))) {
  15719.             itoa((int)lParam1, itoa_buf, 10);
  15720.             WinDdeInitiate(hwnd, itoa_buf, "Graphics_Exchange");
  15721.         }
  15722.     }
  15723.     DosFreeSeg(PDDEITOSEL(DDEInitPtr));
  15724.  
  15725.  break;
  15726.  
  15727.  Figure 18
  15728.  
  15729.  case WM_DDE_INITIATE:  /* respond if app and topic strings match */
  15730.       pDDEInit = (PDDEINIT)lParam2;
  15731.  
  15732.     /* check for null strings or Graphics_Exchange */
  15733.     if ((HWND)lParam1 != hwnd) {
  15734.         itoa((int)hwnd, szTemp, 10);
  15735.         if(((!strlen(pDDEInit->pszAppName)) &&
  15736.             (!strcmp("Graphics_Exchange", pDDEInit->pszTopic))) ||
  15737.             ((!strcmp(szTemp, pDDEInit->pszAppName)) &&
  15738.             (!strcmp("Graphics_Exchange", pDDEInit->pszTopic))) ||
  15739.             ((!strlen(pDDEInit->pszTopic)) &&
  15740.             (!strlen(pDDEInit->pszAppName))))    {
  15741.  
  15742.             /* create conversation window and respond */
  15743.             hwndConv = WinCreateWindow(hwndDDE,
  15744.                                        (PSZ)"DDEConversation",
  15745.                                        (PSZ)NULL, WS_VISIBLE,
  15746.                                        0,0,0,0, hwnd, HWND_TOP,
  15747.                                        ++nConvID, (PVOID)NULL,
  15748.                                        (PVOID)NULL);
  15749.             pWWVar->ConvCnt++;
  15750.             WinDdeRespond(lParam1, hwndConv, "Client",
  15751.                           "Graphics_Exchange");
  15752.          }
  15753.       }
  15754.       DosFreeSeg(PDDEITOSEL(pDDEInit));
  15755.  
  15756.  break;
  15757.  
  15758.  Figure 19
  15759.  
  15760.  case WM_DDE_INITIATEACK:  /* establish conversation with server */
  15761.       if( ((!strcmp("Client", DDEInitPtr->pszAppName)) &&
  15762.            (!strcmp("Graphics_Exchange", DDEInitPtr->pszTopic))) ||
  15763.          ((!strlen(DDEInitPtr->pszAppName)) &&
  15764.            (!strcmp("Graphics_Exchange",DDEInitPtr->pszTopic)))) {
  15765.  
  15766.           /* create a window for the conversation  -
  15767.              child of DDEanchorHWND  */
  15768.  
  15769.           DDEconversationHWND = WinCreateWindow(hwnd, (PSZ)"DDE_Win",
  15770.                                  (PSZ)NULL, WS_VISIBLE, 0, 0, 0, 0,
  15771.                                  WinQueryWindow(hwnd,QW_PARENT,FALSE),
  15772.                                  HWND_TOP, ++winDDEid, (PVOID)NULL,
  15773.                                  (PVOID)NULL);
  15774.           WinSetWindowULong(DDEconversationHWND, WW_CONV_HWND,
  15775.                             (ULONG)lParam1);
  15776.           WinSetWindowULong(WinQueryWindow(hwnd, QW_PARENT, FALSE),
  15777.                             WW_CONVCOUNT,
  15778.                             WinQueryWindowULong(
  15779.                                 WinQueryWindow(
  15780.                                     hwnd,QW_PARENT,
  15781.                                     FALSE),
  15782.                             WW_CONVCOUNT)+1);
  15783.  
  15784.           /* send a request for initial data */
  15785.           DDEstrptr = st_DDE_Alloc(sizeof(DDESTRUCT) +
  15786.                         strlen("Graphics")+1,"DDEFMT_graphics_data");
  15787.           DDEstrptr->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15788.           strcpy(DDES_PSZITEMNAME(DDEstrptr), "Graphics");
  15789.           WinDdePostMsg((HWND)lParam1, DDEconversationHWND,
  15790.                         (ULONG)WM_DDE_REQUEST, DDEstrptr, TRUE);
  15791.  
  15792.           /* send an advise to subscribe to
  15793.              receive future data updates */
  15794.  
  15795.           DDEstrptr = st_DDE_Alloc(sizeof(DDESTRUCT) +
  15796.                         strlen("Graphics")+1,"DDEFMT_graphics_data");
  15797.           DDEstrptr->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15798.           strcpy(DDES_PSZITEMNAME(DDEstrptr), "Graphics");
  15799.           WinDdePostMsg((HWND)lParam1, DDEconversationHWND,
  15800.                         (ULONG)WM_DDE_ADVISE, DDEstrptr, TRUE);
  15801.       }
  15802.       /* free the memory */
  15803.       DosFreeSeg(PDDEITOSEL(DDEInitPtr));
  15804.  
  15805.  break;
  15806.  
  15807.  Figure 20
  15808.  
  15809.  case WM_DDE_REQUEST: /* allocate DDESTRUCT and dump graphics data */
  15810.       pDDEStruct = (PDDESTRUCT)lParam2;
  15811.       strcpy(szTemp, "Graphics");
  15812.       if((pDDEStruct->usFormat == pWWVar->usFormat) &&
  15813.           (!strcmp(szTemp, DDES_PSZITEMNAME(pDDEStruct)))) {
  15814.            if(!pWWVar->bNoData) {
  15815.               nNumBytes = (strlen(szTemp) + 1 + sizeof(GDEDATA) +
  15816.                            LOUSHORT(lPhFigCnt));
  15817.               pDDEStruct = st_DDE_Alloc((sizeof(DDESTRUCT) +
  15818.                             nNumBytes), "DDEFMT_graphics_data");
  15819.               pDDEStruct->cbData = sizeof(GDEDATA) + lPhFigCnt;
  15820.               pDDEStruct->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15821.               pDDEStruct->offabData = (USHORT)((sizeof(DDESTRUCT) +
  15822.                             strlen(szTemp)) + 1);
  15823.               pGDEData = (PGDEDATA)DDES_PABDATA(pDDEStruct);
  15824.               st_Init_GDEData(pGDEData);
  15825.               pGDEData->cBytes = lPhFigCnt;
  15826.               strcpy(pGDEData->szItem, "phone");
  15827.               pGDEData->pGpi = (unsigned char far *)((LONG)pGDEData +
  15828.                                 sizeof(GDEDATA));
  15829.               GpiGetData(hpsGraphics, (LONG)IDSEG_PHONE,
  15830.                          (PLONG)&lOffset, DFORM_NOCONV,
  15831.                          (LONG)lPhFigCnt, (PBYTE)pGDEData->pGpi);
  15832.               memcpy(DDES_PSZITEMNAME(pDDEStruct), szTemp,
  15833.                      (strlen(szTemp) + 1));
  15834.               if(lParam1 != WinQueryWindow(hwnd, QW_OWNER, FALSE)) {
  15835.                  pDDEStruct->fsStatus |= DDE_FRESPONSE;
  15836.                  pWWVar->hwndLink = (HWND)lParam1;
  15837.               }
  15838.               WinDdePostMsg(pWWVar->hwndLink, hwnd, WM_DDE_DATA,
  15839.                             pDDEStruct, TRUE);
  15840.            }
  15841.            else {
  15842.               WinDdePostMsg(pWWVar->hwndLink, hwnd, WM_DDE_DATA,
  15843.                             NULL, TRUE);
  15844.            }
  15845.            DosFreeSeg(PDDESTOSEL(lParam2));
  15846.       }
  15847.       else {  /* post negative ACK using their DDESTRUCT */
  15848.            pDDEStruct->fsStatus &= (~DDE_FACK);
  15849.  
  15850.  WinDdePostMsg(lParam1, hwnd, WM_DDE_ACK, pDDEStruct, TRUE);
  15851.  
  15852.  }
  15853.  
  15854.  break;
  15855.  
  15856.  Figure 21
  15857.  
  15858.  case WM_TIMER:  /* insert phone message into listbox,
  15859.                     update picture, and generate new
  15860.                     REQUESTS for all ADVISING windows */
  15861.       .
  15862.       .  /* listbox and picture have been updated */
  15863.       .
  15864.       hEnum = WinBeginEnumWindows(hwndDDE);
  15865.       while((hwndEnum = WinGetNextWindow(hEnum))) {
  15866.  
  15867.  pWWChild = (PWWVARS)WinQueryWindowULong(hwndEnum, QWL_USER);
  15868.  
  15869.  if (pWWChild->bAdvise) {
  15870.              pDDEStruct = st_DDE_Alloc(sizeof(DDESTRUCT) +
  15871.                            strlen("Graphics")+1,
  15872.                            "DDEFMT_graphics_data");
  15873.              pDDEStruct->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15874.              strcpy(DDES_PSZITEMNAME(pDDEStruct), "Graphics");
  15875.              WinDdePostMsg(hwndEnum, hwnd, WM_DDE_REQUEST,
  15876.                            pDDEStruct, TRUE);
  15877.          }
  15878.          WinLockWindow(hwndEnum, FALSE);
  15879.       }
  15880.       WinEndEnumWindows(hEnum);
  15881.  
  15882.  break;
  15883.  
  15884.  
  15885.  
  15886.  Figure 22
  15887.  case WM_DDE_ADVISE:  /* set ADVISE bit in window data and ACK */
  15888.       pDDEStruct = (PDDESTRUCT)lParam2;
  15889.       if(pDDEStruct->usFormat == pWWVar->usFormat) {
  15890.          pWWVar->hwndLink = (HWND)lParam1;
  15891.          pWWVar->bAdvise = TRUE;
  15892.          if(pDDEStruct->fsStatus & DDE_FNODATA) {
  15893.            pWWVar->bNoData = TRUE;
  15894.          }
  15895.          if(pDDEStruct->fsStatus & DDE_FACKREQ) {
  15896.             pDDEStruct->fsStatus |= DDE_FACK;
  15897.             WinDdePostMsg(pWWVar->hwndLink, hwnd, WM_DDE_ACK,
  15898.                           pDDEStruct, TRUE);
  15899.          }
  15900.          else {
  15901.             DosFreeSeg(PDDESTOSEL(lParam2));
  15902.          }
  15903.       }
  15904.       else {    /* Send a negative ACK using their DDEStruct */
  15905.          pDDEStruct->fsStatus &= (~DDE_FACK);
  15906.          WinDdePostMsg(lParam1, hwnd, WM_DDE_ACK, pDDEStruct, TRUE);
  15907.       }
  15908.       break;
  15909.  
  15910.  case WM_DDE_UNADVISE:/* turn off ADVISE bit in window data and ACK */
  15911.  
  15912.  pDDEStruct = (PDDESTRUCT)lParam2;
  15913.     if((lParam1 == pWWVar->hwndLink) &&
  15914.         (pDDEStruct->usFormat == pWWVar->usFormat)) {
  15915.          pWWVar->bAdvise       = FALSE;
  15916.          pWWVar->bNoData       = TRUE;
  15917.          pDDEStruct->fsStatus |= DDE_FACK;
  15918.          WinDdePostMsg(lParam1, hwnd, WM_DDE_ACK, pDDEStruct, TRUE);
  15919.       }
  15920.     else {
  15921.          pDDEStruct->fsStatus &= (~DDE_FACK);
  15922.          WinDdePostMsg(lParam1, hwnd, WM_DDE_ACK, pDDEStruct, TRUE);
  15923.     }
  15924.  
  15925.  break;
  15926.  
  15927.  
  15928.  
  15929.  Figure 23
  15930.  
  15931.  DDEstrptr = (PDDESTRUCT)lParam2;
  15932.  
  15933.  switch (message){
  15934.  
  15935.     case WM_DDE_DATA:   /* process incoming picture */
  15936.         gde_ptr = (PGDEDATA)DDES_PABDATA(DDEstrptr);
  15937.         gde_ptr->hwnd_idItem = LOUSHORT(hwnd);
  15938.  
  15939.         /*  add if request  * /
  15940.         if (DDEstrptr->fsStatus && DDE_FRESPONSE) {
  15941.             WinSendMsg(WinWindowFromID(
  15942.                        WinQueryWindow(hwnd, QW_OWNER, FALSE),
  15943.                        ID_GRAPHICS1),IC_INSERTITEM,
  15944.                        MPFROMSHORT(ICM_END), (MPARAM)gde_ptr);
  15945.         } else {  /* replace if advise */
  15946.             WinSendMsg(WinWindowFromID(
  15947.                        WinQueryWindow(hwnd,QW_OWNER,FALSE),
  15948.                        ID_GRAPHICS1),IC_SETITEMSTRUCT,
  15949.                        MPFROMSHORT(gde_ptr->hwnd_idItem),
  15950.                        (MPARAM)gde_ptr);
  15951.         }
  15952.  
  15953.         if (DDEstrptr->fsStatus && DDE_FACKREQ) {
  15954.             DDEstrPtrAck = st_DDE_Alloc(sizeof(DDESTRUCT),
  15955.                             "DDEFMT_graphics_data");
  15956.             WinDdePostMsg((HWND)lParam1, hwnd, (ULONG)WM_DDE_ACK,
  15957.                             DDEstrPtrAck, TRUE);
  15958.         }
  15959.  
  15960.         DosFreeSeg(PDDESTOSEL(DDEstrptr));
  15961.  
  15962.  break;
  15963.  
  15964.  Figure 24
  15965.  
  15966.  /* send WM_DDE_UNADVISE, shutdown all conversations
  15967.     for this application, then quit */
  15968.  case WM_CLOSE:
  15969.     if (WinQueryWindowULong(hwnd, WW_CONVCOUNT)) {
  15970.         WinSetWindowULong(hwnd, WW_CLOSE,
  15971.                           WinQueryWindowULong(
  15972.                            hwnd, WW_CLOSE) | WIN_CLOSING_FLAG);
  15973.         henum = WinBeginEnumWindows(DDEanchorHWND);
  15974.         while (hwndenum = WinGetNextWindow(henum)) {
  15975.             WinSetWindowULong(hwndenum, WW_CONV_FLAGS,
  15976.                       WinQueryWindowULong(hwndenum, WW_CONV_FLAGS) |
  15977.                       WIN_TERM_FLAG);
  15978.  
  15979.  tohwnd = (HWND)WinQueryWindowULong(hwndenum,WW_CONV_HWND);
  15980.  
  15981.  DDEptr = st_DDE_Alloc(sizeof(DDESTRUCT) +
  15982.                         strlen("Graphics")+1,"DDEFMT_graphics_data");
  15983.             DDEptr->offszItemName = (USHORT)sizeof(DDESTRUCT);
  15984.             strcpy(DDES_PSZITEMNAME(DDEptr), "Graphics");
  15985.             WinDdePostMsg(tohwnd, hwndenum, WM_DDE_UNADVISE,
  15986.                           DDEptr, TRUE);
  15987.             WinDdePostMsg(tohwnd, hwndenum, WM_DDE_TERMINATE,
  15988.                           NULL, TRUE);
  15989.             WinLockWindow(hwndenum, FALSE);
  15990.           }
  15991.           WinEndEnumWindows(henum);
  15992.       }
  15993.       else {
  15994.           WinPostMsg(hwnd,WM_QUIT,0L,0L); /* quit if no
  15995.                                              conversations open  */
  15996.       }
  15997.  
  15998.  break;
  15999.  
  16000.  Figure 25
  16001.  
  16002.  /* post terminate to server, tell client, and die */
  16003.  case WM_DDE_TERMINATE:
  16004.  
  16005.  if (!WinQueryWindowULong(WinQueryWindow(hwnd, QW_OWNER, FALSE),
  16006.  
  16007.  WW_CLOSE)) {
  16008.         WinDdePostMsg((HWND)lParam1, hwnd, WM_DDE_TERMINATE,
  16009.                       NULL, TRUE);
  16010.     }
  16011.     WinPostMsg(WinQueryWindow(hwnd,QW_OWNER,FALSE),
  16012.                APPM_CONV_CLOSE,MPFROMLONG(hwnd),(MPARAM)NULL);
  16013.     WinDestroyWindow(hwnd);
  16014.  
  16015.  break;
  16016.  
  16017.  Figure 26
  16018.  
  16019.  /* decrement conversation count and delete picture */
  16020.  case APPM_CONV_CLOSE:
  16021.     WinSetWindowULong(hwnd,WW_CONVCOUNT,
  16022.                       WinQueryWindowULong(hwnd, WW_CONVCOUNT) - 1);
  16023.     WinSendMsg(WinWindowFromID(hwnd,ID_GRAPHICS1),IC_DELETEITEM,
  16024.                                (MPARAM)LOUSHORT(lParam1),
  16025.                                (MPARAM)NULL);
  16026.     if (WinQueryWindowULong(hwnd,WW_CLOSE) &&
  16027.         !WinQueryWindowULong(hwnd, WW_CONVCOUNT)){
  16028.             WinPostMsg(hwnd,WM_QUIT,0L,0L);
  16029.     }
  16030.  
  16031.  break;
  16032.  
  16033.  
  16034.  
  16035.  
  16036.  Creating a Virtual Memory Manager to Handle More Data in Your Applications
  16037.  
  16038.   Marc Adler
  16039.  
  16040.  The amount of memory that is available to a program under the Microsoft(R)
  16041.  OS/2 operating system is beginning to spoil many programmers. For example,
  16042.  when Magma's ME Programmer's Text Editor (not to be confused with the
  16043.  Microsoft Editor) was ported to OS/2, one of the advantages was the ability
  16044.  to easily edit files larger than the available memory. Going back to the DOS
  16045.  version of the editor, with its limited file size, became very difficult. To
  16046.  satisfy the desire to edit very large files under DOS, the DOS version of ME
  16047.  had to be enhanced. The logical way to do that was to design and build a
  16048.  virtual memory manager (VMM) that could handle the demand. Figure 1 lists
  16049.  the APIs for the VMM.
  16050.  
  16051.  The motivation for writing a virtual memory manager was enhanced by a desire
  16052.  to overcome the shortcomings of malloc, the staple function of the C
  16053.  run-time library. Different compiler manufacturers have implemented the C
  16054.  memory allocation functions in different ways, each implementation being
  16055.  equally mysterious to the average programmer.
  16056.  
  16057.  From a programming perspective, it is advantageous to take the mysteries out
  16058.  of malloc and to put the inner workings of a memory management function at
  16059.  programmers' fingertips, to be tinkered with in special situations and to be
  16060.  traced meaningfully with the Microsoft CodeView(R) debugger. Being able to
  16061.  do such tracing and tinkering might be very useful, for example, if one felt
  16062.  that a program had corrupted a chain of allocated blocks of memory.
  16063.  
  16064.  Finally, there was an overriding desire to ensure that ME would never again
  16065.  be limited by the amount of free memory left in a given system.
  16066.  Unbelievably, there are still people who are using 256Kb machines. The
  16067.  amount of memory that DOS itself takes up combined with both the memory used
  16068.  by terminate-and-stay-resident (TSR) programs and the huge size of the EXE
  16069.  files that make up today's major applications, severely limits the amount of
  16070.  space that can be allocated, even with 640Kb of memory.
  16071.  
  16072.  One specific goal in designing the virtual memory manager for the ME text
  16073.  editor was to keep it simple enough and general enough so that it could be
  16074.  used in any other application that needed memory management beyond what
  16075.  malloc offers. Since many people have installed additional memory boards in
  16076.  their systems, it was also desirable to be able to exploit the capabilities
  16077.  of expanded memory in the virtual memory manager. (Even though ME's virtual
  16078.  memory manager supports EMS, its implementation won't be covered here. Such
  16079.  an implementation, however, would be useful as an exercise for the reader.)
  16080.  
  16081.  The VMM must be compiled in large model. The reason is that we must remain
  16082.  consistent with the far pointer that we use and the pointers that some of
  16083.  the C library routines need (such as memset). A little work needs to be done
  16084.  if you would like to use the VMM under a different memory model. (The actual
  16085.  VMM source code listings, VM.H and VM.C, are available on MSJ's bulletin
  16086.  boards and are not included here due to space constraints--Ed.)
  16087.  
  16088.  Interspersed throughout the text will be comments about possible extensions
  16089.  you could implement to make the VMM more powerful. If you decide to
  16090.  implement any of the suggestions, I'd be interested in receiving changes,
  16091.  along with your comments about how they affected the performance of the VMM.
  16092.  They should be forwarded to the Technical Editor of Microsoft Systems
  16093.  Journal. (Interesting additions and comments may be published by MSJ in a
  16094.  future issue.--Ed.)
  16095.  
  16096.  Figure 1
  16097.  
  16098.   VMInit
  16099.  
  16100.  Initializes the VMM. Must be called at the beginning of the application
  16101.  before any memory is requested.
  16102.  
  16103.    VMTerminate
  16104.  
  16105.  Shuts down the VMM. Call it at the end of your application.
  16106.  
  16107.    char far *MemDeref(HANDLE h)
  16108.  
  16109.  Dereferences the object pointed to by handle h and returns the memory
  16110.  address of that object.
  16111.  
  16112.    HANDLE MyAlloc(unsigned size)
  16113.  
  16114.  Allocates size bytes from the VMM and returns a handle to that block. The
  16115.  block is also filled with zeros.
  16116.  
  16117.    MyFree(HANDLE h)
  16118.  
  16119.  Frees the memory block pointed to by handle h.
  16120.  
  16121.    SetVMPageSize(int kbytes)
  16122.  
  16123.  Sets the default page size to kbytes kilobytes. For instance,
  16124.  SetVMPageSize(16) sets the default page size to 16Kb.
  16125.  
  16126.    MakePageDirty(HANDLE h)
  16127.  
  16128.  Sets the dirty bit of the page that contains the memory block referenced by
  16129.  handle h.
  16130.  
  16131.  Initializing the VMM
  16132.  
  16133.  The first thing every program that uses the virtual memory manager must do
  16134.  is to initialize it. This is done simply by calling the function VMInit. The
  16135.  main job of VMInit is to create the swap file that is used when blocks of
  16136.  memory must be paged out to disk. VMInit will first check to see if the user
  16137.  defined an environment variable called METEMP. METEMP should contain the
  16138.  path name of where the swap file should go. If the METEMP variable is not
  16139.  defined, the swap file will be created in the current directory. The use of
  16140.  a user-definable destination for the swap file allows the user to take
  16141.  advantage of any RAMdisks that might be available. Swapping to a RAMdisk, of
  16142.  course, is significantly faster than swapping to a hard disk.
  16143.  
  16144.  You can set the METEMP variable with a line in your AUTOEXEC.BAT file that
  16145.  looks like this :
  16146.  
  16147.  set METEMP=<swap path>
  16148.  
  16149.  To create the name of the swap file, you use the mktemp function, which is
  16150.  part of the C run-time library. This function takes a single parameter, a
  16151.  string representing a file name template, and creates a unique file name
  16152.  from that template. In this case, the template that is used is VMXXXXXX. The
  16153.  mktemp function will replace the uppercase Xs with characters that would
  16154.  make the file name unique in the current directory. For instance, mktemp
  16155.  might return the string VM065291 to VMInit, and we would use that as the
  16156.  name of our swap file.
  16157.  
  16158.  At this point, I must confess that the virtual memory manager has one major
  16159.  limitation; the swap space is bounded by the available space on the swap
  16160.  disk (plus the amount of expanded memory that is free). A possible extension
  16161.  would be to allow the VMM to use multiple volumes when swapping, including
  16162.  swapping over a network to a totally different computer (such as a remote
  16163.  file server). If you do this, beware of the DOS limitation on the number of
  16164.  files that an application can have open simultaneously.
  16165.  
  16166.  Terminating the VMM
  16167.  
  16168.  Before terminating, an application must call VMTerminate to do some cleanup
  16169.  work. VMTerminate simply closes the swap file and deletes it. If you added
  16170.  routines to do performance analysis or to swap to multiple volumes, you
  16171.  might need to do some cleanup work at this point.
  16172.  
  16173.  Obtaining Memory
  16174.  
  16175.  The function that obtains memory from the VMM and returns it to the caller
  16176.  is MyAlloc. It replaces the standard call to malloc. Since the memory
  16177.  returned is zeroed out, MyAlloc can replace calloc as well. A single
  16178.  argument is passed to MyAlloc--the number of bytes needed--and a
  16179.  handle is returned.
  16180.  
  16181.  The word handle is becoming an increasingly popular term, mainly because of
  16182.  its frequent usage in Microsoft Windows. A handle is simply an identifier
  16183.  that is associated with a block of memory; it is used by the internal
  16184.  routines to identify an object. In the true spirit of data hiding, the value
  16185.  of a handle (also referred to as a magic cookie) will typically have no
  16186.  meaning to the application that uses the VMM.
  16187.  
  16188.  In the case of our VMM, a handle is an unsigned long quantity comprised of
  16189.  two parts. The upper 16 bits is the page number in which the block of memory
  16190.  is found, and the lower 16 bits constitutes the zero-based offset from the
  16191.  beginning of that page. For example, a handle whose value is 00030400H
  16192.  signifies that the memory block is located 400H bytes away from the
  16193.  beginning of the block allocated for page 3. Figure 2 illustrates this
  16194.  example.
  16195.  
  16196.  This design allows us to have at most 64Kb pages, each page containing as
  16197.  many as 64Kb of memory. Thus our VMM can access a total of more than 4Gb,
  16198.  well over the maximum size of the largest hard disk.
  16199.  
  16200.  The Structure of a Page
  16201.  
  16202.  As I stated earlier, MyAlloc expects a single argument that represents the
  16203.  amount of memory we want to allocate from the VMM. If we cannot find a page
  16204.  with enough free memory, a new page will be allocated. At this point, let's
  16205.  see what is in the PAGE data structure.
  16206.  
  16207.  You'll find the declaration of the PAGE data structure (shown in Figure 3)
  16208.  in the listing of VM.H. Since a block can reside anywhere in RAM, we need a
  16209.  field to hold its far address. A block can reside on disk, so we also need a
  16210.  field to hold the offset from the beginning of the swap file where the block
  16211.  is found. These two fields are called memaddr and diskaddr.
  16212.  
  16213.  In addition to these two fields, we have fields that contain the ID of the
  16214.  page (a unique integer), the size of the page (in case we modify the VMM to
  16215.  deal with different sized pages), a bit mask to represent the status of the
  16216.  page (if it's in memory, on disk, or both, and whether the page is dirty),
  16217.  the offset to the first free byte in the page (used for implementing a
  16218.  linked list of free blocks within the page), and the clock for the least
  16219.  recently used (LRU) swapping algorithm. We also have fields for the number
  16220.  of free bytes within the page and the size of the largest free block of
  16221.  contiguous memory. These two fields can be used in conjunction, in the event
  16222.  we modify the VMM to do true compaction of free blocks.
  16223.  
  16224.  The amount of space allocated for a page should be a power of 2. With
  16225.  careful experimentation, you might optimize the performance for your
  16226.  application. A routine known as SetVMPageSize is provided that allows the
  16227.  application to set the page size from within your application. It should be
  16228.  called directly after VMInit. If you decide to alter the default size of a
  16229.  page, then you must call SetVMPageSize only once within your application,
  16230.  and the call should be made before any pages are actually allocated. The
  16231.  reason for this is that the swapping algorithm thinks that each page is the
  16232.  same size. We use a default size of 4Kb for each page; however, in the
  16233.  version that uses expanded memory, the page size is increased to 16Kb to
  16234.  match the size of an expanded memory page.
  16235.  
  16236.  Within each page, a linked list of the free blocks within that page is
  16237.  maintained. This list is defined by the FREEINFO structure. Each free block
  16238.  (and allocated block) has a header that records the number of bytes in the
  16239.  block and the offset to the next free block in the page. A block that has
  16240.  the value of FFFFH in its offset field is the last block in the chain. This
  16241.  linked list is a simple one with no implicit ordering of blocks. We will
  16242.  talk about enhancing this list when we discuss the freeing of blocks. Figure
  16243.  4 shows an example of a typical chain of free blocks.
  16244.  
  16245.  When MyAlloc is called, we search the page list for the first page with the
  16246.  necessary amount of contiguous free bytes. The function that handles this
  16247.  search is FindNContigBytesFree. Precedence is given to pages that are
  16248.  already in memory, but if there are no pages in memory that contain the
  16249.  needed bytes, we look for the first disk-based page that has the free space.
  16250.  If there are no pages either on disk or in memory that have a sufficient
  16251.  number of contiguous bytes free, we allocate a new page and return its
  16252.  address. The AllocPage routine is responsible for allocating space for a new
  16253.  page header and for the associated buffer.
  16254.  
  16255.  Back in MyAlloc, we have a pointer to a page with the necessary number of
  16256.  bytes free, and we are assured that the page is in conventional memory. We
  16257.  then traverse the list of free blocks within that page and stop when we find
  16258.  the first block with the necessary free bytes. The block is zeroed out and
  16259.  the handle is returned to the calling routine.
  16260.  
  16261.  Figure 3
  16262.  
  16263.  typedef struct page
  16264.  {
  16265.    struct page *next;            /* chain to next free page in list */
  16266.    char far *memaddr;            /* memory address of the page block */
  16267.    unsigned long diskaddr;       /* disk address of the page block */
  16268.    PAGEID   id;                  /* page identifier */
  16269.    unsigned long LRUcount;       /* least-recently-used count      */
  16270.    unsigned pagesize;            /* how many bytes is this page    */
  16271.    unsigned freebyte;            /* index of 1st free byte in page */
  16272.    unsigned bytesfree;           /* # of bytes free in this page   */
  16273.    unsigned maxcontigfree;       /* max # of contiguous free bytes */
  16274.  
  16275.    unsigned flags;
  16276.  #define PAGE_IN_MEM     0x0001
  16277.  #define PAGE_ON_DISK    0x0002
  16278.  #define IS_DIRTY        0x0004
  16279.  #define SET_PAGE_DIRTY(p)  ((p)->flags |= IS_DIRTY)
  16280.  #define NON_SWAPPABLE   0x0008
  16281.  #define PAGE_IN_EMM     0x0010
  16282.  } PAGE;
  16283.  
  16284.  
  16285.  
  16286.  Swapping Pages
  16287.  
  16288.  In this version of the VMM, a call is made to the Microsoft C library
  16289.  routine, _dos_allocmem, to allocate memory for a page. This routine is
  16290.  really a front end for the main DOS memory allocation service (Int 21H,
  16291.  function 48H). Using the DOS memory functions lets us totally bypass the
  16292.  malloc family found in the C run-time library. (Actually, each page header
  16293.  is allocated using malloc, but we can choose to use DOS memory for this if
  16294.  we want.) We continue to use _dos_allocmem to grab space for a page until we
  16295.  run out of memory. At this point, we have a page header allocated for the
  16296.  new page but no block of memory allocated for its data. What we need to do
  16297.  is borrow the memory used by a previously allocated page. But before that
  16298.  can be done, we must save that page's data somewhere. Then the new page can
  16299.  use that page's block of memory to store its own data in. This process is
  16300.  called swapping or paging.
  16301.  
  16302.  How do we know just where in the swap file the old page's contents should be
  16303.  stored? The array VMFile.slottable contains a map of which sectors of the
  16304.  swap file are used by which pages. A NULL entry for a sector means that the
  16305.  sector is free. When we swap a page to disk for the first time, we search
  16306.  the slot table for the first NULL entry and then write the page to the
  16307.  corresponding sector.
  16308.  
  16309.  The remaining question is, How do we determine which page to swap out to
  16310.  disk? If we have a bad algorithm for choosing the swappable page, we can run
  16311.  into a hideous phenomenon know as thrashing. If a VMM thrashes, it is
  16312.  spending an inordinate amount of time swapping pages between disk and
  16313.  memory. That can happen if we choose to swap out a frequently referenced
  16314.  page.
  16315.  
  16316.  For our swapping algorithm, we use the old, time-tested least recently used
  16317.  algorithm. In order to implement the algorithm, we must keep a clock which
  16318.  is incremented every time a page is accessed. Each page has a variable which
  16319.  records the time when it was last accessed. To find the LRU page, we just
  16320.  scan the page list for the page with the minimum clock time and return a
  16321.  pointer to that page.
  16322.  
  16323.  A possible enhancement to the VMM is to implement a means of making certain
  16324.  important pages nonswappable. For instance, instead of using malloc to
  16325.  allocate the headers for each page as we do now, we could allocate memory
  16326.  from the VMM for them. However, it would be disastrous if the page headers
  16327.  were swapped out to disk! In this case, we might use a nonswappable page to
  16328.  hold the headers and other important system information. The LRU algorithm
  16329.  would require a simple modification that would tell it not to consider pages
  16330.  marked as nonswappable.
  16331.  
  16332.  Another possible enhancement is to keep the list of pages in a doubly linked
  16333.  list rather than a singly linked one and to maintain a pointer to the tail
  16334.  of the list. Since we move a page to the head of the page list whenever we
  16335.  access that page, we will be guaranteed that the least recently used page
  16336.  will be at the tail of the list. Using this method, we find the LRU page
  16337.  merely by looking at the tail; we don't need to implement a clock for the
  16338.  LRU algorithm, and we don't have to allocate a counter for each page in the
  16339.  system.
  16340.  
  16341.  A Perfect Fit?
  16342.  
  16343.  The method of allocation followed here is called the first fit approach.
  16344.  It's given that name because we stop our search at the first block that fits
  16345.  the criterion, that is, the first block that has enough contiguous bytes
  16346.  free. Another popular approach is called best fit; we traverse the entire
  16347.  free list in search of a block with the smallest size that satisfies our
  16348.  criterion. A third method is called next fit; we remember the position the
  16349.  last block that was allocated came from, and the next time we search for a
  16350.  free block, we start at that spot. The first fit method has the advantage of
  16351.  taking less time to find the proper memory block, and best fit has the
  16352.  advantage of reducing fragmentation (if you use one of the methods of
  16353.  reducing fragmentation discussed below).
  16354.  
  16355.  Fragmentation of memory is a major concern when designing a memory manager.
  16356.  It is caused when you allocate part of a memory block that has more free
  16357.  bytes than you really need: part of that block will be allocated and the
  16358.  remainder will be put back onto the free list; however, if the remaining
  16359.  block is too small for any subsequent allocation request, it may never be
  16360.  allocated. For example, let's say that I need a block of 24 free bytes, and
  16361.  after searching the chain of free blocks, I come to a block that has 32
  16362.  bytes free. I will allocate 24 bytes out of that block and leave a block of
  16363.  8 bytes on the free list. This free block is probably too small to satisfy
  16364.  any future allocation request, so it will remain forever on the free list.
  16365.  That may not seem so bad, except that the memory manager will still have to
  16366.  examine the block whenever it searches the free list; multiply this example
  16367.  by the thousands of memory requests that a typical application might make
  16368.  and you'll see why fragmentation is a severe problem. Figure 5 illustrates a
  16369.  graphic representation of fragmentation.
  16370.  
  16371.  A simple way of solving fragmentation problems is to round off an allocation
  16372.  request to a higher number; the excess bytes will be included in the free
  16373.  block that we return to the application. For the example above, I might use
  16374.  the simple heuristic of rounding off all allocations to the nearest power of
  16375.  2. If I ask for 24 bytes, and I come upon a block of 32 bytes, then I will
  16376.  return the entire block to the application. Although 8 bytes may never be
  16377.  used, I will not have a small block floating about in the free list.
  16378.  
  16379.  Another solution is to perform periodic garbage collection and periodic
  16380.  compaction on the memory blocks, an approach we will also consider.
  16381.  
  16382.  Dereferencing Handles
  16383.  
  16384.  You will recall that the handle to a memory block is merely an identifier
  16385.  that tells the VMM how to reference that block; the application does not
  16386.  really know what memory address the handle points to. Once an application
  16387.  obtains a handle to a memory object, it has to go through a dereferencing
  16388.  step in order to use the memory block that the handle refers to.
  16389.  
  16390.  Remember, too, that a handle is an unsigned long value that comprises the
  16391.  page identifier and the offset within that page. The function MemDeref must
  16392.  be called whenever you need to transform a handle into a memory address. For
  16393.  example, if we wanted to copy the string XYZ into an allocated memory block,
  16394.  we must write the following code:
  16395.  
  16396.  #include "vm.h"
  16397.  
  16398.  ■
  16399.  ■
  16400.  ■
  16401.  
  16402.  HANDLE h;
  16403.  char far *s;
  16404.  
  16405.  ■
  16406.  ■
  16407.  ■
  16408.  
  16409.  
  16410.  /* Note--this assumes
  16411.     large memory model */
  16412.  if ((h = MyAlloc(4)) ==
  16413.      (HANDLE) NULL)
  16414.  /* perform error
  16415.     processing */
  16416.  
  16417.  ■
  16418.  ■
  16419.  ■
  16420.  
  16421.  
  16422.  if ((s = MemDeref(h)) !=
  16423.          NULL)
  16424.  strcpy(s, "XYZ");
  16425.  
  16426.  MemDeref simply breaks the handle into its constituent parts, searches for
  16427.  the page with the ID contained in the handle, swaps the page into memory if
  16428.  necessary, adds the base memory address of the page with the offset of the
  16429.  block that the handle specifies, and returns a pointer to the memory
  16430.  address. One nice thing that MemDeref also does is to move the referenced
  16431.  page to the front of the linked list of pages that are currently in use. The
  16432.  VMM assumes a locality of reference within an application--that is,
  16433.  once a page is referenced, it will continue to be referenced in the
  16434.  application's surrounding code. Moving the newly referenced page to the
  16435.  front of the page list reduces the time needed to traverse the page list for
  16436.  subsequent searches for that page.
  16437.  
  16438.  Touching a Page
  16439.  
  16440.  If you look carefully at the code for the WritePage function, you'll notice
  16441.  that a page will get written to disk only if it's currently in memory and if
  16442.  the page has been modified since it was allocated or last written to disk.
  16443.  If the same image of a page exists both in memory and in the swap file,
  16444.  there is no need to perform the actual write to disk.
  16445.  
  16446.  The VMM includes a routine that sets the dirty bit of the page that contains
  16447.  a referenced memory block. The MakePageDirty function takes a single
  16448.  argument, the handle of a memory block, and searches for the page
  16449.  corresponding to that block. When that page is located, its dirty bit is
  16450.  set. This scheme ensures that when we modify a certain portion of memory,
  16451.  the associated page will be swapped properly when memory begins to run low.
  16452.  
  16453.  Referring to the example above, if we wanted to change the bytes pointed to
  16454.  by handle h to the string ABC, we would need to do the following :
  16455.  
  16456.  s = MemDeref(h);
  16457.  strcpy(s, "ABC");
  16458.  MakePageDirty(h);
  16459.  
  16460.  Freeing a Memory Block
  16461.  
  16462.  When a memory block is no longer needed, we call the MyFree routine to
  16463.  release it back into the memory pool. MyFree takes a single
  16464.  argument--the handle to the memory block that we will release. To
  16465.  release a memory block, we simply place it on the page's linked list of free
  16466.  blocks and increase the number of free bytes within that page. What makes
  16467.  the operation a little more complicated is the fact that we would like to
  16468.  examine the blocks adjacent to the newly freed block, and if they are free,
  16469.  coalesce them with the newly freed block. When coalescing is done, our most
  16470.  important statistic, the number of contiguous free bytes, also increases.
  16471.  
  16472.  To increase how quickly we traverse the linked list of free blocks and to
  16473.  help us examine adjacent blocks, we keep the list sorted by the blocks'
  16474.  offsets. When we free a block, we traverse the list until we find a free
  16475.  block whose offset is greater than that of the newly freed block. Then we
  16476.  insert the newly freed block in the list before this block. Figure 6
  16477.  illustrates a typical coalescing action.
  16478.  
  16479.  The astute reader will notice that although the user can theoretically
  16480.  request up to 64Kb of memory (the maximum value of the argument to MyAlloc),
  16481.  the amount of memory requested is limited to the size of the page block. To
  16482.  accommodate this limit, we can modify the VMM to dynamically modify the
  16483.  basic page block size if a request comes in that is too large.
  16484.  
  16485.  The Double Indirection Method
  16486.  
  16487.  Earlier, we alluded to the fact that periodic compaction of the memory
  16488.  blocks would reduce fragmentation in our VMM. Before compaction, the free
  16489.  blocks are intermingled with the allocated blocks, and the free list must be
  16490.  traversed in order to locate a block with the desired amount of contiguous
  16491.  bytes free. After compaction, all the allocated blocks are pushed to one
  16492.  side of the page, and a single large free block is created out of the
  16493.  remaining space. When we look for a free block to allocate, there is only
  16494.  one block to examine in the page.
  16495.  
  16496.  Compaction presents one major problem, however. If we move an allocated
  16497.  block to another position in memory, we must modify all our application's
  16498.  variables that point to that block to point to the new place in memory. But
  16499.  how does the memory manager know which variables point to that memory block?
  16500.  
  16501.  To solve this problem, we use a scheme known as double indirection. Double
  16502.  indirection has been made popular by the memory managers of both the
  16503.  Macintosh(R) and of Microsoft Windows. When an application requests a block
  16504.  of memory, we will return not a pointer to that block, but a pointer to a
  16505.  pointer to that block. This is illustrated in Figure 7.
  16506.  
  16507.  Figure 8 illustrates what happens when we compact a number of memory blocks.
  16508.  Even though the blocks shift in memory, the pointers to the blocks remain
  16509.  stationary. Since all our application knows about are the indirect pointers,
  16510.  and since the position of those pointers doesn't change, the memory manager
  16511.  does not have to alert the application when the compaction operation occurs.
  16512.  Everything is totally invisible to the application. (Actually, in Microsoft
  16513.  Windows Version 2.x, an application can choose to be alerted when allocated
  16514.  blocks are moved by specifying the GMEM_NOTIFY flag in the call to
  16515.  GlobalAlloc.)
  16516.  
  16517.  As you can see from the diagrams in Figures 7 and 8, the double indirection
  16518.  method requires one extra memory reference in order to dereference a memory
  16519.  handle. However, with the speed of modern day CPUs increasing yearly, the
  16520.  extra memory reference is not as much of a problem as it used to be. If you
  16521.  consider the reduction of time involved in searching  the free list, and
  16522.  also take into account the elimination of fragmentation, you'll see why the
  16523.  designers of Windows chose the doubly indirect way of doing these things.
  16524.  
  16525.  The McBride Allocator
  16526.  
  16527.  Let's take a brief look at another virtual memory management package, VMEM
  16528.  by Blake McBride. VMEM (see Figure 9), which also comes with full source
  16529.  code, uses the double indirection method to achieve high performance.
  16530.  Although it does not support expanded memory at this time, it compensates
  16531.  for this deficiency by allowing compaction of the swap file. It also uses
  16532.  one of the enhancements that was mentioned above--it maintains the
  16533.  virtual memory objects on a doubly linked list so that there is always a
  16534.  pointer to the least recently used object.
  16535.  
  16536.  The user has a choice of two methods of swap file compression. Using the
  16537.  first method, all objects are moved to the beginning of the swap file, so
  16538.  that one large hole remains at the end. The second method uses two swap
  16539.  files; all allocated objects are copied from the first swap file to a newly
  16540.  created second swap file, then the original swap file is deleted. Although
  16541.  both methods produce the same results, each method has an important
  16542.  advantage. With the single file method, even though the allocated blocks
  16543.  have been moved, the size of the swap file will never decrease. With the
  16544.  dual-file method, you need twice the disk storage during the compression
  16545.  operation.
  16546.  
  16547.  VMEM also has the ability to save and restore entire virtual memory images
  16548.  to and from disk. This capability is ideal if, for example, you'd like to
  16549.  save the entire state of the virtual memory system, release the memory back
  16550.  to DOS, shell out a large program (like a compiler), and restore the state
  16551.  of the system when the shelled program is completed.
  16552.  
  16553.  A study of the VMEM API will reveal that you have a bit more control over
  16554.  the operating parameters with VMEM than you do with our simple memory
  16555.  allocator. The API lets you choose whether you want to clear the memory when
  16556.  you allocate, set the dirty bit of a block when you deference that block,
  16557.  and directly change some of the important operating parameters of VMEM.
  16558.  
  16559.  Using a VMM as a replacement for heap-based allocation gives an application
  16560.  more flexibility in dealing with huge amounts of data. I believe that
  16561.  virtual memory is best done at the operating system level (for example,
  16562.  using Microsoft OS/2) and kept invisible from all applications in the
  16563.  system. But since a large part of the market is, and will remain for some
  16564.  time to come, firmly entrenched in the DOS world, virtual memory managers
  16565.  are still needed. In the future, all operating systems will have built-in
  16566.  virtual memory and will make use of the dedicated memory management chips
  16567.  produced by semiconductor vendors like Intel and Motorola. Let's hope that
  16568.  that day is not too far away.
  16569.  
  16570.  Figure 9
  16571.  
  16572.  char far *VM_addr(VMPTR_TYPE voh, int dirty, int fFreeze)
  16573.  
  16574.  Dereferences the memory handle voh. The variable dirty should not be zero if
  16575.  you are going to change the contents of the memory block. fFreeze is not
  16576.  zero if the block's position in memory should be frozen.
  16577.  
  16578.  VMPTR_TYPE VM_alloc(long size, int fClear)
  16579.  
  16580.  Allocates size bytes from the memory pool. If fClear is not zero, the memory
  16581.  block will be cleared to zeros.
  16582.  
  16583.  VM_dcmps
  16584.  
  16585.  Initiates compression of the swap file. The compression method may be
  16586.  selected with VM_parm.
  16587.  
  16588.   int VM_dump(char *filename)
  16589.  
  16590.  Dumps the entire contents of the VM system to the disk file filename.
  16591.  Returns 0 if the dump was successful, nonzero if not.
  16592.  
  16593.  void VM_end
  16594.  
  16595.  Terminates VMEM. The swap file is deleted, but real memory is not released
  16596.  back to the operating system.
  16597.  
  16598.  void VM_fcore
  16599.  
  16600.  Same as VM_end, except that all real memory is returned to the operating
  16601.  system.
  16602.  
  16603.  void VM_free(VMPTR_TYPE voh)
  16604.  
  16605.  Frees the object referenced by handle voh.
  16606.  
  16607.  int VM_rest(char *filename)
  16608.  
  16609.  int VM_frest(char *filename)
  16610.  
  16611.  Restores the VMM to a previous state that was saved by VM_dump. The main
  16612.  difference between VM_rest and VM_frest is that the VM_rest will read in the
  16613.  memory blocks only as they are needed and is therefore much faster than
  16614.  VM_frest.
  16615.  
  16616.   int VM_init
  16617.  
  16618.  Initializes VMEM.
  16619.  
  16620.  void VM_parm(long rmmax, long rmasize, double rmcompf, long dmmfree, int
  16621.  dmmfblks, int dmctype)
  16622.  
  16623.  Sets various VMEM parameters. Used to fine-tune the system. The arguments
  16624.  are the following:
  16625.  
  16626.  rmmax    --    maximum amount of real memory that VMEM will request
  16627.  
  16628.  rmasize    --    minimum amount of real memory that VMEM will request
  16629.  
  16630.  rmcompf    --    real memory compression factor
  16631.  
  16632.  dmmfree    --    determines when automatic swap file compression occurs
  16633.  
  16634.  dmmfblks    --    another method used to determine when swap file
  16635.  
  16636.          compression occurs
  16637.  
  16638.  dmmctype    --    type of swap file compression used
  16639.  
  16640.  VMPTR_TYPE VM_realloc(VMPTR_TYPE voh, long newsize)
  16641.  
  16642.  Reallocates the memory block pointed to by voh and returns a handle to the
  16643.  new block.
  16644.  
  16645.  long *VM_stat
  16646.  
  16647.  Used to obtain various statistics about the current state of VMEM.
  16648.  
  16649.  
  16650.  Using the OS/2 Video I/O Subsystem to Create Appealing Visual Interfaces
  16651.  
  16652.   Richard Hale Shaw
  16653.  
  16654.  The most noticeable attribute of an application is the way it visually
  16655.  interacts with the user. If the application looks snappy and smart, the user
  16656.  will gain confidence when running it for the first time. If the application
  16657.  appears slow and dull, however, the user will become apprehensive or, worse
  16658.  yet, bored. Thus, from the user's perspective, the screen is the most
  16659.  essential mechanism of the application. To an OS/2 application developer,
  16660.  this makes the video I/O (VIO) the most important of the three subsystems
  16661.  available to OS/2 character-based applications.
  16662.  
  16663.  An Overview of VIO
  16664.  
  16665.  The MS-DOS(R) operating environment provided such limited and inefficient
  16666.  video services, that application developers looked for other means to
  16667.  improve the video throughput of their programs. A great number of DOS
  16668.  applications took advantage of the PC ROM BIOS video services (Int 10h) or
  16669.  wrote to and directly manipulated the video hardware and display buffer.
  16670.  This approach hindered portability, but it was not a problem under the real
  16671.  mode of the DOS operating environment, since only one application at a time
  16672.  was able to access the video hardware.
  16673.  
  16674.  In contrast, OS/2 systems are endowed with a robust highly efficient set of
  16675.  video services, which comprise the VIO subsystem. The subsystem consists of
  16676.  a set of character-oriented display services of the type that are generally
  16677.  required by the current generation of character-based applications. Think of
  16678.  VIO as a superset of the PC ROM BIOS services (Int 10h) found in real mode
  16679.  under DOS, with the difference being that VIO uses calls instead of an
  16680.  interrupt.
  16681.  
  16682.  The efficiency and effectiveness of the VIO services are such that there is
  16683.  little need for an application to manipulate the video hardware directly.
  16684.  Perhaps the only reason an OS/2 application would need to access the video
  16685.  hardware while using VIO would be to generate graphics images. VIO has only
  16686.  limited graphics support, but it does allow an application to move to and
  16687.  from graphics mode (this will be discussed in greater detail below). In the
  16688.  event that an application does need to access the video hardware, OS/2 can
  16689.  serialize such accesses and provide a means for applications to do so that's
  16690.  compatible with its protected mode environment.
  16691.  
  16692.  Although it's essential that a real operating system like OS/2 offer
  16693.  efficient video services (where DOS does not), there is another, more
  16694.  profound reason for the existence of VIO. The protected mode of the OS/2
  16695.  operating environment requires that system facilities be virtualized and
  16696.  shared among processes, including access to the video screen and video
  16697.  hardware. An application should not be hindered from performing screen
  16698.  updates while running in the background, but a foreground application should
  16699.  not have its screen trashed by a program running in the background. By using
  16700.  VIO, the user is assured that the video output of one application will not
  16701.  interfere with that of another application. In addition, most VIO calls can
  16702.  be used in bound programs that must run under both OS/2 and DOS. Like the
  16703.  ROM BIOS calls or direct video hardware control code under DOS, VIO calls
  16704.  under OS/2 make a program less portable to the DOS environment and more OS/2
  16705.  specific. Of course, this is true of all non-FAPI OS/2 calls.
  16706.  
  16707.  VIO is implemented as a dynamic-link library (found in VIOCALLS.DLL), so a
  16708.  program's references to VIO services are bound at execution time, not at
  16709.  link time. Thus, you can replace VIO functions at any time without
  16710.  recompiling or relinking the client application. This is how the OS/2
  16711.  Presentation Manager (PM) handles VIO calls. By providing its own version of
  16712.  VIOCALLS.DLL, PM can allow non-PM OS/2 programs to run under it--even
  16713.  in the PM screen group (that is, in a PM window).
  16714.  
  16715.  VIO calls are efficient, but they do not offer the complex formatting
  16716.  facilities of the printf standard library function nor do they handle output
  16717.  redirection. A subsystem like VIO must be fast, so it's necessary that it
  16718.  avoid the encumbrances of the file system and I/O redirection. In those
  16719.  instances in which redirection is a consideration, however, you can use
  16720.  DosRead and DosWrite. These two kernel services use VIO to handle the screen
  16721.  component of their output. Note that when STDOUT points to a screen device,
  16722.  OS/2 routes it through VIO.
  16723.  
  16724.  If generic screen output is a consideration, you'll find it simpler and
  16725.  easier to use the standard C library routines, since a typical application
  16726.  will probably use a combination of these and VIO calls. The VIO services do,
  16727.  however, allow considerable flexibility in the way text is written to the
  16728.  screen, including control over color and cursor positioning.
  16729.  
  16730.  The Logical Video Buffer
  16731.  
  16732.  As mentioned earlier, OS/2 considers the screen a resource that is able to
  16733.  be simultaneously shared by more than one process. When a session is
  16734.  started, OS/2 creates a logical video buffer (LVB) for it. OS/2 retains
  16735.  control of the physical video buffer (PVB). If a program makes a VIO call,
  16736.  VIO will update the LVB of the program's session and notify OS/2 that the
  16737.  PVB must be updated. While a session is in the foreground, OS/2 will
  16738.  duplicate VIO updates of the LVB in the PVB. Thus, VIO calls always update
  16739.  the two buffers while a session is in the foreground. When you move the
  16740.  session into the background, OS/2 assumes that its screen contents are
  16741.  completely updated in the LVB. Therefore, it doesn't have to save the screen
  16742.  and can quickly update the PVB from the new foreground session's LVB. This
  16743.  makes screen switches very fast and allows OS/2 to virtualize screen access
  16744.  among processes.
  16745.  
  16746.  While the session is in the background, VIO services will continue to write
  16747.  to the session's LVB. OS/2 ignores calls from VIO to update the PVB, since
  16748.  the foreground session is using the PVB. When the session is brought into
  16749.  the foreground again, OS/2 will copy the contents of the LVB to the PVB and
  16750.  resume updating the PVB from the LVB. Thus, as long as the application uses
  16751.  VIO services to do its video updates, the LVB will always accurately depict
  16752.  an application's visual state in character mode. This remains true whether
  16753.  the application is in the foreground or background, and it makes VIO a safe
  16754.  means of updating the video screen.
  16755.  
  16756.  Each LVB is organized in 2-byte pairs, called cells (shown in Figure 1). The
  16757.  number of cells in the LVB will vary with the current video mode (for
  16758.  example, a 43-line mode will require more cells than a 25-line mode). Each
  16759.  cell corresponds to one character on the screen, with the first byte of each
  16760.  cell containing the character itself and the second byte holding the
  16761.  attribute value (which controls foreground and background color, intensity,
  16762.  and blinking). You can use different VIO services to write characters,
  16763.  attributes, or cells to the video screen.
  16764.  
  16765.  The VIO subsystem supports the full range of PC-based video adapters and
  16766.  displays, including monochrome, CGA, EGA, and VGA.  The 24 different flavors
  16767.  of text and graphics modes that are available are shown in Figure 2. The
  16768.  smallest user-addressable unit of the screen is a character in text mode or
  16769.  a pixel or pel in graphics mode. PC display hardware is memory mapped, so
  16770.  whatever is currently stored in video memory (the PVB mentioned above) is
  16771.  displayed on the screen. Other than the description of the video buffers
  16772.  given above, however, it's not essential that you know how video memory is
  16773.  organized or where it's physically located, unless you're going to access
  16774.  the video hardware directly. Also, note that unlike DOS, OS/2 doesn't use
  16775.  mode numbers to specify a video mode. When getting or setting the video
  16776.  mode, you deal directly with the video characteristics themselves.
  16777.  
  16778.  As previously mentioned, the attribute byte of each pair or cell controls
  16779.  the colors, intensity, and blinking characteristics of a character displayed
  16780.  on screen. Of the 8 bits in an attribute byte, the lower 3 bits control the
  16781.  foreground color, a value of 0-7. Bit 3 toggles intensity on or off,
  16782.  bits 4-6 control the background color, and bit 7 toggles the blinking
  16783.  characteristic. These are shown in Figures 3 and 4.
  16784.  
  16785.  VIO and PM
  16786.  
  16787.  As you are probably well aware, an OS/2 system offers a high-powered
  16788.  graphical user interface called the Presentation Manager. PM offers a
  16789.  graphical presentation space, windowing, scroll bars, icons, and interfaces
  16790.  for both a mouse and the keyboard. It runs in its own screen group under
  16791.  OS/2 and can run other programs in PM windows in the same screen group.
  16792.  These capabilities differ from those of VIO applications under OS/2 Version
  16793.  1.0, where a background process remains in its own screen group and is not
  16794.  visible until a user brings it into the foreground screen group. PM can also
  16795.  run ill-behaved programs (that is, those programs that do not run in a PM
  16796.  window or that write directly to the PVB), but they will run in their own
  16797.  screen group like any other OS/2 program.
  16798.  
  16799.  Although the Presentation Manager can do all of the things mentioned, there
  16800.  is still a need for VIO. First, PM applications are complex. They take more
  16801.  effort to write since simple visual ideas can be complex to express in PM
  16802.  code. Second, some applications (a command-line utility, for instance) do
  16803.  not require PM or wouldn't benefit very much from PM's graphical user
  16804.  interface. Other applications will have to be redesigned or completely
  16805.  rethought before they can take advantage of PM. These are problems that VIO
  16806.  can easily solve. In addition, VIO offers a path to OS/2 that requires
  16807.  minimal changes to existing DOS applications. Thus, it's considerably easier
  16808.  to adapt a DOS application to VIO than to PM.
  16809.  
  16810.  VIO-based programs will run under a PM text window. That's because the
  16811.  versions of VIO (and, for that matter, KBD and MOU) offered with PM (OS/2
  16812.  Version 1.1) are different from those supplied with OS/2 Version 1.0. The
  16813.  VIO DLL supplied with PM is windows aware and can map each character and its
  16814.  attributes into the appropriate pattern of pixels inside a PM window, while
  16815.  clipping the output of each VIO call to fit the application's window size.
  16816.  The entire process is invisible to both the programmer and the compiled
  16817.  program.
  16818.  
  16819.  If you use VIO in your OS/2 applications today, you won't pay an extensive
  16820.  penalty later. As OS/2 is ported to other processor families, VIO calls will
  16821.  remain the same and should not require changes to those portions of an
  16822.  application that use VIO. Future releases of OS/2 for the 80286 family of
  16823.  processors won't require a recompile, since new versions of VIO will be
  16824.  supplied with each release. This is why VIO is implemented as a dynamic-link
  16825.  package.
  16826.  
  16827.  As this series continues, I'll include practical tips that will make it
  16828.  easier for you to produce efficient, high-quality programs that take
  16829.  advantage of OS/2 facilities. If you refer back to the second article in the
  16830.  series, "Planning and Writing a Multithreaded OS/2 Program With Microsoft(R)
  16831.  C," MSJ (Vol. 4, No. 2), there is a discussion of some of the ways a
  16832.  multithreaded program can use up too many CPU cycles, making it difficult
  16833.  for other processes to run (particularly those in the background). You'll
  16834.  see another example of this in the program listing for this article. From
  16835.  the perspective of using OS/2's video system, however, keep in mind that a
  16836.  well-behaved OS/2 program is one that confines itself to the use of VIO
  16837.  (most standard library functions call VIO) and does not access the PVB.
  16838.  
  16839.  VIO Data Structures
  16840.  
  16841.  Earlier in this series, I briefly discussed the new OS/2 header files,
  16842.  structures, and type definitions. Because of the variety of objects used to
  16843.  manipulate the screen, there are several data structures designed strictly
  16844.  for VIO programming. These are displayed in Figure 5, which you can refer to
  16845.  as you read about the sample program listing.
  16846.  
  16847.  The VIO data structures provide all of the information used by the old
  16848.  IBM(R) PC ROM BIOS calls, and more. But instead of passing information in
  16849.  registers (the convention in the DOS environment), OS/2 places the
  16850.  information in a structure whose address is passed to the appropriate
  16851.  function. Not only does this make retrieving and changing the video
  16852.  environment rather easy, it places a machine-independent interface on the
  16853.  entire mechanism. Moreover, in keeping with other OS/2 functions, it allows
  16854.  the function to return a single unsigned integer as an error return status.
  16855.  
  16856.  For example, the program listing contains a function (Cursor) that can save
  16857.  or restore the cursor. Notice that it uses the VIOCURSORINFO object type,
  16858.  which includes the starting and ending cursor scan lines, the cursor width
  16859.  (in columns or pixels depending on the current screen mode), and the cursor
  16860.  character attribute. Information in the VIOCURSORINFO object is easily
  16861.  retrieved and/or modified via the VioGetCurType and VioSetCurType functions,
  16862.  as shown in the listing.
  16863.  
  16864.  The VIO Pop-up Facility
  16865.  
  16866.  VIO offers a means by which a background process can temporarily pop up in
  16867.  the foreground or a foreground process can temporarily switch to a blank
  16868.  text screen. If you note the stress on temporary, you'll begin to see that
  16869.  the need for pop-ups is not the same as with DOS. Under DOS, a
  16870.  terminate-and-stay-resident (TSR) application could take control of the
  16871.  processor and pop up over the current application for an indefinite period
  16872.  of time. Since DOS is a single-tasking environment, this became an
  16873.  acceptable manner of communicating with a TSR program. And since the current
  16874.  application was frozen until the TSR gave up control of the processor, the
  16875.  TSR could stay popped up for as long as  you wished.
  16876.  
  16877.  Under OS/2, the characteristics of a TSR program in the DOS environment are
  16878.  no longer needed since you can run an application in another screen group
  16879.  and switch that screen group into the foreground whenever, as often, and for
  16880.  however long you like. From the perspective of accessing a TSR program,
  16881.  every program is a TSR under OS/2. It's always there, running in its own
  16882.  private screen group, although you can still issue the commands to the
  16883.  application for it to terminate. This is even more obvious under PM, since
  16884.  PM and well-behaved VIO programs that run in the PM screen group can
  16885.  simultaneously display and run in overlapping windows.
  16886.  
  16887.  Remember that a process can be detached and run in a special screen group,
  16888.  where it does not have to have regular keyboard input or show screen
  16889.  displays. Detached processes are good candidates for pop-up programs under
  16890.  OS/2. You'll doubtless come up with other ideas.
  16891.  
  16892.  Pop ups are exceptions to the way that OS/2 typically handles the console.
  16893.  Only one pop-up program can be activated at a time: if another process
  16894.  issues a pop-up call, it will be blocked until the first one relinquishes
  16895.  the screen. While a pop-up program is active, the user cannot switch to
  16896.  another process or screen group. In this sense, the pop-up program gains
  16897.  temporary ownership of the foreground screen.
  16898.  
  16899.  The pop-up facility of OS/2 should only be used by a process that needs to
  16900.  gain temporary control of the physical console (that is, the screen, the
  16901.  keyboard, or the mouse). Since other processes cannot be switched into the
  16902.  foreground during a pop up, the pop-up program must be efficient, do its
  16903.  job, and end the pop-up session quickly. A good example of how the OS/2
  16904.  pop-up facility is used is the OS/2 Hard Error handler. You can observe its
  16905.  use of the pop-up facility by issuing a DIR command on a disk drive while
  16906.  the drive door is open.
  16907.  
  16908.  When a pop-up program begins, the screen is automatically placed in 80 by 25
  16909.  text mode. The screen is restored to the previous mode when the pop-up
  16910.  program ends. The VIO service routines for activating a pop-up program are
  16911.  VioPopUp and VioEndPopUp. The function prototypes for these two routines are
  16912.  shown in Figure 6. Note that only a subset of VIO services are available
  16913.  during the pop-up call (shown in Figure 7). An example of code that
  16914.  activates a pop-up program can be found in the graphics function in the
  16915.  sample program listing.
  16916.  
  16917.  Using the VIO API
  16918.  
  16919.  Any discussion of the VIO Application Programming Interface (API) ought to
  16920.  begin with a reference to Ray Duncan's article, "Character-Oriented Display
  16921.  Services Using OS/2's VIO Subsystem," MSJ (Vol. 2, No. 4). It contains an
  16922.  excellent overview of the API and discusses most of the services that VIO
  16923.  provides. This article will now pick up where Duncan's article left off and,
  16924.  in doing so, will walk through the sample program listing presented here.
  16925.  The sample is a modified and enhanced version of HELLO0.C, which was
  16926.  presented in the preceding article in this series. This discussion will
  16927.  provide insight into the practical application of VIO services.
  16928.  
  16929.  A New HELLO.C
  16930.  
  16931.  Recall that HELLO0.C was a multithreaded version of HELLO.C. It was designed
  16932.  to illustrate the use of threads and semaphores to control and serialize
  16933.  access to resources. Although technically sound, it was visually boring, so
  16934.  adding a variety of colors to it is a good idea. In addition, the current
  16935.  version of HELLO1.C (The actual code listing is available on MSJ's bulletin
  16936.  boards and is not included here due to space constraints--Ed.) has been
  16937.  restructured and modularized to allow different sample functions to be
  16938.  plugged in with ease.
  16939.  
  16940.  Probably the first thing you'll notice in HELLO1.C is that the macro
  16941.  INCL_SUB has been defined at the beginning of the program listing. As such,
  16942.  the program automatically includes the function prototypes, macros, and type
  16943.  definitions that will be needed to use VIO functions via the system of
  16944.  header files and defines that were discussed in the earlier article.
  16945.  
  16946.  Next, note that every VIO function requires a device handle as its last
  16947.  argument. In non-PM versions of OS/2, this handle is always zero. A special
  16948.  set of VIO calls under PM (called advanced VIO, or AVIO) use this argument.
  16949.  The argument is represented by a macro, VIOHDL, in the listings here, making
  16950.  it easy to search for and replace references to it later.
  16951.  
  16952.  Finally, in fitting with the convention used by all OS/2 API functions,
  16953.  notice that VIO services that return an error code will return a nonzero
  16954.  unsigned value when an error occurs. You can always assume that a VIO
  16955.  service was successful when it returns a zero value. Also note that one of
  16956.  the most commonly used VIO routines, VioWrtTTy, is now called VioWrtTTY in
  16957.  OS/2 Version 1.1, which has been released since this series was initiated.
  16958.  
  16959.  The Startup Function
  16960.  
  16961.  As you see from the listing, main has been simplified to include only a
  16962.  handful of functions. The first of these, startup, processes the optional
  16963.  command-line arguments. As in HELLO0.C, these arguments are the number of
  16964.  milliseconds used in calls to DosSleep, which allow you to vary the sleep
  16965.  time between thread events. If you set these values too low, the program
  16966.  will hog CPU time and radically slow down OS/2 and other programs. These
  16967.  arguments are discussed in more detail below.
  16968.  
  16969.  Startup performs several other services for the program.  It initiates a
  16970.  keyboard thread (discussed below). Then it calls the Screen function to save
  16971.  the current video display for restoration when the program terminates.
  16972.  Screen takes a single argument, which determines if the screen should be
  16973.  saved or restored. It assumes that the calling program's screen group is in
  16974.  text mode and calls VioGetBuf to retrieve the address of the screen group's
  16975.  LVB and the size of the buffer. Thus Screen can allocate storage to hold the
  16976.  LVB contents and copy the contents of the LVB to the new storage. To restore
  16977.  the display when the program terminates, Screen copies the contents of the
  16978.  storage buffer back to the LVB and calls VioShowBuf to update the physical
  16979.  display. This process is further discussed in the sidebar, "Direct Screen
  16980.  I/O and Graphics under VIO".
  16981.  
  16982.  Startup also calls the Cursor function to hide the cursor and save the
  16983.  cursor position. Like Screen, the Cursor function takes a single argument,
  16984.  whose bits determine whether Cursor should hide, save, or restore the cursor
  16985.  (that is, position it and make it visible). It uses VioGetCurType to
  16986.  retrieve the cursor character attribute and VioSetCurType to change the
  16987.  attribute (and hide the cursor). It calls VioGetCurPos to save the current
  16988.  cursor position and VioSetCurPos to reposition the cursor. Note that the
  16989.  Screen and Cursor functions rely on the VIOCURSORINFO data type.
  16990.  
  16991.  Startup also resets the screen rows to the highest number possible. It does
  16992.  this by calling VioGetMode to get the current video mode information (into a
  16993.  VIOMODEINFO object type). Then it sets the row parameter to either 50, 43,
  16994.  or 25 lines and calls VioSetMode until it is successful. This lets the
  16995.  program run at 50 lines on a VGA monitor, 43 lines on an EGA monitor, and 25
  16996.  lines otherwise. The original row count is saved so the termination function
  16997.  can restore it before the program exits.
  16998.  
  16999.  After startup, main calls the initialization function shown in the listing.
  17000.  This code is virtually identical to the code used by HELLO0.C in the
  17001.  preceding article and initializes the FRAME structures.
  17002.  
  17003.  Flickering Frames
  17004.  
  17005.  The flickering frames of HELLO0.C that greeted you with "Hello, World from
  17006.  thread" are now encapsulated in the flicker_frames function. The code for
  17007.  this function is very similar to that in the original program. The function
  17008.  will create about two dozen threads; the exact number will vary with the
  17009.  number of screen rows. Each thread carries the responsibility of displaying
  17010.  or clearing its frame on cue from its semaphore, which is stored in its
  17011.  FRAME data type. And each thread shares the code found in the hello_thread
  17012.  function.
  17013.  
  17014.  Hello_thread now calls VioWrtCharStrAtt to do its screen I/O. This is a VIO
  17015.  service routine that writes a string at a specified row and column location
  17016.  with a specific attribute. Note that an attribute byte for each frame (and
  17017.  thus each frame thread) has been added to the FRAME data type and is set and
  17018.  manipulated by flicker_frames. Thus, whenever each frame appears it has a
  17019.  different foreground and background color. The flicker_frames function masks
  17020.  off the blinking bit in the attribute so that the frames do not blink. In
  17021.  addition, the function increments the FRAME attribute byte whenever the
  17022.  foreground and background colors match,  thereby ensuring that the
  17023.  foreground and background contrast.
  17024.  
  17025.  In order to accommodate the changes to the FRAME type, the program now
  17026.  includes a filler byte. This ensures that the threadstack member is aligned
  17027.  on an even-numbered address. The _beginthread library routine will not
  17028.  successfully create a thread whose stack falls on an odd-numbered address.
  17029.  
  17030.  The operator initiates the termination of the flicker_frames function by
  17031.  pressing the Esc key. This activates the keyboard thread, which is contained
  17032.  in the keyboard_thread function mentioned above. This thread blocks on
  17033.  keyboard input and continues to block until the Esc key is pressed. Then it
  17034.  blocks on a semaphore, doneSem, until flicker_frames clears the semaphore.
  17035.  Clearing the semaphore allows keyboard_thread to begin the termination
  17036.  sequence and flicker_frames to postpone the sequence until it has completed
  17037.  a round of activating the frame threads.
  17038.  
  17039.  Once the semaphore clears, keyboard_thread continues and sets the done flag,
  17040.  thereby notifying flicker_frames that it can terminate the frame threads.
  17041.  Flicker_frames will cause each thread to run one final time, clear its box
  17042.  (if it is not already cleared), and terminate. As each frame thread
  17043.  terminates, it clears its own frame semaphore to notify flicker_frames that
  17044.  it is terminating. In the meantime, flicker_frames calls the
  17045.  WaitForThreadDeath function, which blocks on each thread's semaphore, and
  17046.  returns to the flicker_frames when all the threads have terminated. Then
  17047.  flicker_frames returns to main.
  17048.  
  17049.  Chasing Frames
  17050.  
  17051.  New to HELLO1.C are chasing frames, which demonstrate an animated use of the
  17052.  VIO scrolling routines. Encapsulated in the chasing_frames function, the
  17053.  chasing frames are a series of Hello, World frames that are launched from
  17054.  the upper-right-hand corner of the screen and traverse the screen perimeter
  17055.  counterclockwise. Each successive circuit takes place one frame height and
  17056.  width inside the last, so that the frames gradually make their way to the
  17057.  center of the screen. Another frame follows after a period of time (which is
  17058.  set by one of the command-line sleeptime arguments). The VIO scrolling func
  17059.  tions, VioScrollLf, VioScrollDn, VioScrollRt, and VioScrollUp, not only
  17060.  cause the frames to chase each other, they also leave a trail of each
  17061.  frame's color behind. Each chasing frame is managed by its own thread,
  17062.  thereby giving a visual confirmation to the reality of independent threads
  17063.  of execution. The threads share the code found in the box_thread function.
  17064.  
  17065.  Again the keyboard_thread function plays a role in terminating these
  17066.  threads. After servicing flicker_frames, the keyboard thread goes into a
  17067.  loop, blocking on keyboard input and ignoring all but the Esc key. When the
  17068.  Esc key is finally pressed, the keyboard thread will again set the done flag
  17069.  and terminate. This in turn notifies chasing_frames not to start any
  17070.  additional frames. It also notifies those frame threads that have already
  17071.  started to terminate themselves. If a frame thread is in the middle of
  17072.  scrolling around the screen, its sleeptime will fall to zero and it will
  17073.  race to the upper-right-hand corner of its current circuit. Then it will
  17074.  clear its own semaphore and terminate. As flicker_frames did before it,
  17075.  chasing_frames calls WaitForThreadDeath to wait until all the frame threads
  17076.  have died; it then returns to main.
  17077.  
  17078.  There is one catch to the way the keyboard thread works. The user must press
  17079.  the Esc key before the last chasing frame completes its final circuit.
  17080.  Otherwise, the keyboard thread will continue to block until the Esc key is
  17081.  pressed. The next section of the program assumes that the keyboard is
  17082.  available, so the keyboard thread must have terminated by that time.
  17083.  Unfortunately, there is no kernel routine for one thread to kill another. In
  17084.  any case, it would have made keyboard_thread even more complex to cause it
  17085.  to terminate some other way.
  17086.  
  17087.  The Video Configuration
  17088.  
  17089.  The next function called from main is displayconfig. This function calls
  17090.  get_user_name to prompt the user for his or her name. To do this,
  17091.  get_user_name displays a multicolored prompt, and below it, a highlighted
  17092.  input field delimited with two markers. It uses VioWrtCellStr, which writes
  17093.  a series of character-attribute cell combinations from those stored in the
  17094.  cellstr array to create the multicolored prompt. Then it calls VioWrtNCell,
  17095.  which can replicate a single cell, to create the field and markers. Finally,
  17096.  it restores the cursor, sets it to the beginning of the field, and calls the
  17097.  KBD routine, KbdStringIn, to read the user's name. After hiding the cursor,
  17098.  it returns the number of characters read by KbdStringIn.
  17099.  
  17100.  When displayconfig returns from get_user_name, it knows how long the user's
  17101.  name is, but it does not have the name itself. So it calls VioReadCharStr to
  17102.  read the characters directly from the screen. Next, it retrieves the video
  17103.  configuration with VioGetConfig, prints that information on the screen, and
  17104.  returns to main.
  17105.  
  17106.  Background Colors
  17107.  
  17108.  The togglebackground function illustrates the use of two other VIO services:
  17109.  VioReadCellStr and VioWrtNAttr. The former reads one or more cells from the
  17110.  screen. The Background function, which is called by togglebackground, uses
  17111.  it to retrieve the attribute of the character in the upper-left-hand corner
  17112.  of the screen. It then increments or decrements the attribute value
  17113.  (depending on the argument passed to it) and calls VioWrtNAttr to flood the
  17114.  screen with this attribute. Thus, togglebackground can prompt the user to
  17115.  use the arrow keys to increment or decrement the background color. Note that
  17116.  exactly which color it uses the first time depends on the color of the
  17117.  character in the upper-left-hand corner. This may have been set by one of
  17118.  the chasing frames. The Esc key is used to terminate the togglebackground
  17119.  function and return to main. An additional message (discussed below) is
  17120.  printed, warning the user to press only the Esc key while in graphics mode.
  17121.  
  17122.  Generating Graphics
  17123.  
  17124.  The last facet of VIO that HELLO1.C illustrates is how to generate graphics
  17125.  and create a pop-up program under OS/2. The graphics function changes the
  17126.  video mode to CGA graphics with a 320 by 200 resolution and writes a
  17127.  graphical Hi in magenta on a white field. The function creates a separate
  17128.  thread of execution, which saves and restores the screen before and after a
  17129.  screen switch (see the sidebar, "Direct Screen I/O and Graphics under VIO").
  17130.  Finally, it waits for the user to press the Esc key to terminate. If,
  17131.  however, the user presses any key (other than Alt-Esc or Ctrl-Esc), a pop up
  17132.  will be generated that informs the user to press the Esc key.
  17133.  
  17134.  The graphics function works by first retrieving the current video mode
  17135.  information and changing the video mode parameters. It sets them to indicate
  17136.  a video mode of non-monochrome graphics, with four colors, 40 by 25
  17137.  characters, and 320 by 200 pixels. Then it calls VioSetMode to change the
  17138.  video mode, followed by VioGetPhysBuf to retrieve a selector for the CGA
  17139.  video buffer. It then locks the screen with a call to VioScrLock.
  17140.  
  17141.  The function continues by creating a new thread that executes the
  17142.  SaveRestoreScreen function discussed below. It calls clear_graphics_screen
  17143.  to clear the screen and writes a large Hi in the middle of the screen. The
  17144.  screen write uses two loops to traverse a bitmap of character 1s and 0s
  17145.  (stored in the hi_bitmap array) and calls the putpixel function to write a
  17146.  magenta pixel whenever a 1 is encountered in the bitmap. You can get an idea
  17147.  of what this will look like by staring at the hi_bitmap array at the
  17148.  beginning of the listing and unfocusing your eyes slightly.
  17149.  
  17150.  Last of all, graphics calls VioScrUnLock to unlock the screen and goes into
  17151.  a loop. In the loop it calls KbdCharIn to block on keyboard input. If the
  17152.  Esc key is pressed, the function breaks out of the loop and returns to main.
  17153.  Otherwise, it calls VioPopUp to create a blank 80 by 25 text mode screen,
  17154.  prints a message, and waits for a single keystroke. Upon receiving the
  17155.  keystroke, the function calls VioEndPopUp to end the pop up and loop back to
  17156.  KbdCharIn.
  17157.  
  17158.  The graphics function illustrates two important points: how to generate
  17159.  graphics under OS/2 and how to save and restore the screen in graphics mode.
  17160.  When you run the program, try some screen switches and press the wrong key
  17161.  to generate the pop up. You will find that the warning message (at the end
  17162.  of togglebackground) not to press anything but Esc during the graphics
  17163.  display is a taunt to trick the user into generating the pop up. The
  17164.  SaveRestoreScreen function is called by OS/2 each time the wrong key is
  17165.  pressed to save or restore the graphics image and mode.
  17166.  
  17167.  SaveRestoreScreen starts by calling VioGetMode to save the current video
  17168.  mode information. Following that, it goes into a loop and immediately calls
  17169.  VioSavRedrawWait. This call registers the thread with OS/2, causing the
  17170.  thread to block until a screen switch of some kind occurs. At this point, it
  17171.  doesn't matter whether the user switches sessions (using Alt-Esc), brings up
  17172.  the Session Manager (with Ctrl-Esc), or presses an inappropriate key that
  17173.  causes the graphics function to generate a pop up. If you want a screen
  17174.  switch to takes place, OS/2 will activate the SaveRestoreScreen thread by
  17175.  returning from VioSavRedrawWait.
  17176.  
  17177.  Once SaveRestoreScreen returns from VioSavRedrawWait, it quickly saves the
  17178.  screen by copying the contents of the video screen to a buffer. If a screen
  17179.  restore has to take place, the function restores the screen by resetting the
  17180.  video mode and copying the buffer contents back to the video screen.
  17181.  
  17182.  The graphics function uses two support functions. The first,
  17183.  clear_graphics_screen, simply floods the pixels in the video buffer with
  17184.  FFH, turning them white. The second, putpixel, converts pixel row-column
  17185.  coordinates into pixel offsets and stuffs the color bits passed into the
  17186.  appropriate part of a byte into the video buffer.
  17187.  
  17188.  Terminating the Program
  17189.  
  17190.  The termination function completes HELLO1.C. It resets the screen rows to
  17191.  the original number (restoring the original video mode at the same time),
  17192.  calls Screen to restore the screen to its original state, and calls Cursor
  17193.  to restore the cursor. Finally, it calls DosExit to terminate the program.
  17194.  
  17195.  Here's a summary of how to run the program. The program goes through five
  17196.  stages: flickering frames, chasing frames, the name prompt, the
  17197.  configuration display, and graphics. The flickering frames will run
  17198.  indefinitely until Esc is pressed. The chasing frames will continue until
  17199.  the frames are exhausted, but for best results, terminate them before the
  17200.  last frame makes its way to the center. Since a chasing frame normally
  17201.  appears before its predecessor moves off the top line, it should be easy to
  17202.  identify. The number of frames is set in the initialization function and
  17203.  will vary on different video hardware.
  17204.  
  17205.  Once you have entered your name at the name prompt, you can use the Up and
  17206.  Down arrow keys to change the background color. You can continue
  17207.  indefinitely changing the background until you press Esc. When you press
  17208.  Esc, the graphics display will terminate. If you press any other keys, it
  17209.  will generate the pop-up screen. Try recompiling the program with the
  17210.  SaveRestoreScreen thread commented out. The graphics screen will be trashed
  17211.  to some degree if you switch screens without this thread operating.
  17212.  
  17213.  Finally, you may find it interesting to alter the sleeptime variables in the
  17214.  program. Three of these can be altered on the command line. The command line
  17215.  defaults are
  17216.  
  17217.  HELLO1 1 1500 1
  17218.  
  17219.  which places a minimum of 1 millisecond between the activation of each
  17220.  flickering frame, 1500 milliseconds (1.5 seconds) between each chasing
  17221.  frame, and a 1 millisecond pause when a chasing frame returns to the
  17222.  upper-right-hand corner of its circuit. The most interesting results occur
  17223.  when using 0 instead of 1 and a low number (like 100) instead of 1500. Keep
  17224.  in mind, however, that 0 will cause the program to steal a lot of CPU time
  17225.  from other processes. But it's fun to watch the results.
  17226.  
  17227.  This concludes our examination of the OS/2 VIO subsystem. Additional
  17228.  functions and services, which are not essential to a typical OS/2
  17229.  application but should be mentioned, are listed and briefly discussed in
  17230.  Figure 8.
  17231.  
  17232.  You are now ready to tackle VIO. In fact, you can even write some
  17233.  sophisticated applications. With a good feeling for VIO, you'll find the
  17234.  rest of the OS/2 API no more difficult than gaining knowledge of how and
  17235.  when to use the different services. This will become even more apparent in
  17236.  the next article in this series, which switches from visual output to
  17237.  keyboard and mouse control, and will explore the KBD and MOU subsystems.
  17238.  
  17239.  
  17240.  Direct Screen I/O and Graphics Under VIO
  17241.  
  17242.  The OS/2 VIO subsystem does not offer any facilities for a process to access
  17243.  the physical screen directly. This can pose problems if you are porting DOS
  17244.  applications that perform direct screen writes or generate graphics or if
  17245.  you are writing an OS/2 application that generates graphics without the use
  17246.  of Presentation Manager (PM). Fortunately, OS/2 offers two indirect means
  17247.  for accessing the physical screen, depending on the needs of the
  17248.  application.
  17249.  
  17250.  Direct Screen Writes in Text Mode
  17251.  
  17252.  OS/2 systems provide a logical video buffer (LVB) for each screen group, and
  17253.  all VIO services update the LVB. When a screen group is in the background,
  17254.  screen updates can take place without disrupting the screen of the
  17255.  foreground process. When a screen group is in the foreground, OS/2
  17256.  duplicates VIO updates of the LVB in the physical video buffer (PVB).
  17257.  
  17258.  You can easily modify a DOS program that performs text mode direct screen
  17259.  writes to run under OS/2. To do this, however, it's essential that you
  17260.  change the code that updates the PC's video buffer to write to the LVB
  17261.  instead.
  17262.  
  17263.  First, you must call VioGetBuf to retrieve the address of the LVB of your
  17264.  program's screen group. Since the format of the LVB is identical to a PC's
  17265.  text mode video buffer, the direct screen write code remains largely
  17266.  intact--only the destination changes. Note that there is no need to
  17267.  include code that checks for horizontal retrace or that manipulates the
  17268.  video hardware. OS/2 will take care of these details for you. When you are
  17269.  finished updating the LVB, your program should call VioShowBuf to update the
  17270.  PVB from the LVB. VioShowBuf can update all or a portion of the LVB to the
  17271.  PVB.
  17272.  
  17273.  This indirect screen write process remains the same whether your program is
  17274.  in the foreground or background: OS/2 will only honor calls to VioShowBuf
  17275.  when the program is in the foreground and will ignore such calls when a
  17276.  program is in the background. OS/2 will, however, update the PVB from the
  17277.  LVB once the program is switched to the foreground again. Note that although
  17278.  your program only needs to call VioGetBuf once, when it begins, your program
  17279.  can (and should) call VioShowBuf as often as is necessary. VioShowBuf will
  17280.  do nothing while a process is in a background screen group. This is not a
  17281.  problem since OS/2 will update the PVB upon a screen switch.
  17282.  
  17283.  I should mention that VioShowBuf is very fast. I modified the Magma Systems
  17284.  ME Editor to perform screen paging by writing to the LVB and occasionally
  17285.  calling VioShowBuf. This improved the speed of screen page updates and made
  17286.  the calls to VioShowBuf appear every bit as fast as direct screen writes
  17287.  under DOS.
  17288.  
  17289.  VIO function calls in a foreground process automatically update both
  17290.  buffers, since they notify OS/2 that the PVB must be updated. You can see
  17291.  this for yourself by writing a piece of code that first writes some text
  17292.  directly to the LVB, then calls a VIO service routine to write additional
  17293.  text, which overwrites part of the text written in the first step. This
  17294.  updates the LVB and causes OS/2 to update the PVB. Finally, have the code
  17295.  call VioShowBuf or manually switch the program into the background and back
  17296.  again. Until the PVB is updated, only the output of step 2 will appear on
  17297.  the screen. The text written in step 2 that overlays the text written in
  17298.  step 1 will, however, survive the screen update after the call to
  17299.  VioShowBuf. This also illustrates that, in text mode, the LVB always
  17300.  contains the current visual state of an application.
  17301.  
  17302.  Going Around VIO to Generate Graphics
  17303.  
  17304.  Although it's relatively simple to perform direct screen writes in text
  17305.  mode, it's not as easy to work with graphics. Part of the problem is that
  17306.  you can't use the LVB, since it's only available for direct screen writes in
  17307.  text mode. Therefore, if your program is going to generate graphic images,
  17308.  you'll need to access the PVB directly. Fortunately, VIO has a mechanism
  17309.  that allows an application to step around it and manipulate the PVB
  17310.  directly. This makes it easier to port graphics applications from DOS and
  17311.  enables you to write OS/2 graphics applications without the complexity of PM
  17312.  programming.
  17313.  
  17314.  There are a few caveats to be aware of when writing to the PVB. First, a
  17315.  routine can only access one adapter and video mode at a time. To support
  17316.  several adapters in a graphics routine, you'll have to write code that can
  17317.  address each, making the code somewhat hardware dependent.
  17318.  
  17319.  Next, images drawn in the PVB by an application are lost when a screen
  17320.  switch takes place. As I said earlier, you can't use the LVB in graphics
  17321.  mode. Further, there is no built-in mechanism for saving the contents of the
  17322.  PVB when a screen switch takes place. Since it's not built in, you'll have
  17323.  to provide this mechanism yourself. If your program manipulates any of the
  17324.  video hardware, you'll also have to save that information when a screen
  17325.  switch takes place.
  17326.  
  17327.  Finally, an application that accesses the physical screen must run in its
  17328.  own screen group, so it will never run in a PM window. When run in the PM
  17329.  environment, PM gives the program its own full-screen window. If you want it
  17330.  to run in a PM window, you'll have to rewrite the program to take advantage
  17331.  of the PM graphic facilities.
  17332.  
  17333.  With these warnings in mind, there are two problems to solve when writing a
  17334.  program that generates graphics in a VIO context. How do you use the
  17335.  mechanism for obtaining access and writing to the physical screen? And how
  17336.  do you provide a facility to save and restore the screen before and after a
  17337.  screen switch?
  17338.  
  17339.  The Direct Screen Write Process
  17340.  
  17341.  First, consider how to access and write to the physical screen buffer. Your
  17342.  program should call VioSetMode to ensure that the display is in the correct
  17343.  graphics mode. Then you must obtain the address of the PVB by calling
  17344.  VioGetPhysBuf, which will return a selector that corresponds to the address
  17345.  of the physical buffer. When you're ready to write to the buffer, you must
  17346.  use VioScrLock to lock the screen so a background application will not
  17347.  switch screens during the update. Next, your program should perform the
  17348.  direct screen write itself. This should be fast, efficient code, since a
  17349.  screen lock temporarily hangs the system and prevents a screen switch from
  17350.  taking place. Finally, your program must call VioScrUnLock to unlock the
  17351.  screen.
  17352.  
  17353.  Note that once you've retrieved a selector for the physical buffer address,
  17354.  the remaining steps must be taken every time your program is going to access
  17355.  the display hardware and write to the screen. These steps are essential to
  17356.  inhibit screen switches (which will disrupt the display) and to prevent
  17357.  other processes from writing to the display at the same time.
  17358.  
  17359.  VioScrLock is also a fast, effective way to determine whether or not your
  17360.  program is in the foreground, since the caller can either choose to block if
  17361.  the screen is not available or to return immediately. If the screen is
  17362.  unavailable, the program can do other processing until it is. Probably the
  17363.  most effective way to use VioScrLock is to place the direct screen write
  17364.  code in a function that's executed by a separate thread. You can use a
  17365.  semaphore to tell the thread when to perform a screen update, and the thread
  17366.  can block on VioScrLock until the screen can be accessed. This leaves the
  17367.  main thread free to continue with other work and lets you structure the code
  17368.  so that a graphics-intensive I/O can be written quickly, in its entirety,
  17369.  without a great deal of overhead.
  17370.  
  17371.  VioScrLock is so effective in locking the screen that nothing can switch the
  17372.  screen while it's locked. It protects the system from the application and
  17373.  protects the application from the system. Even pop ups (including the hard
  17374.  error handler) cannot be activated during a screen lock. As a safety valve,
  17375.  OS/2 will cancel the lock and perform a screen switch after 30 seconds if a
  17376.  program or the user has requested it. If your graphics application is still
  17377.  drawing an image when the screen switch takes place, the picture will be
  17378.  disrupted.
  17379.  
  17380.  Saving/Restoring the Video Display and State
  17381.  
  17382.  The second problem is to see how an application that manipulates the video
  17383.  hardware can save and restore the entire video state. The contents of the
  17384.  display buffer, the video mode, the palettes, the cursor, and so on, will
  17385.  all be lost if a screen switch takes place. VIO cannot perform this service
  17386.  for you, since a graphics bitmap might require a great deal of memory to
  17387.  save and restore the display. Further, some video hardware adapters have
  17388.  write-only registers that cannot be saved for later restoration--only
  17389.  your program will know whether or not it changed a video register.
  17390.  
  17391.  Thus, OS/2 can only notify a process when a screen switch is about to take
  17392.  place. It becomes the application's job to save and restore the screen when
  17393.  so notified. This is accomplished by creating a thread whose job it is to
  17394.  save and restore the screen. You can register the thread function with OS/2;
  17395.  then, just before the screen switch takes place, OS/2 will activate the
  17396.  thread. This lets you construct a thread to do one job and do it
  17397.  well--to save or restore the screen and video mode at the appropriate
  17398.  time. Thus, the thread's structure, minus the details of saving/restoring
  17399.  the display and video mode, is quite simple. The thread enters a loop and
  17400.  immediately calls VioSaveRedrawWait. This function will cause the thread to
  17401.  block until OS/2 indicates that a screen switch is about to take place. When
  17402.  this happens OS/2 will cause VioSaveRedrawWait to return, at which time the
  17403.  thread must perform the screen save or restoration. Returning to the top of
  17404.  the loop, the thread again calls VioSaveRedrawWait, which notifies OS/2 that
  17405.  the screen switch can continue. The thread will continue to block until the
  17406.  next screen switch for this process occurs.
  17407.  
  17408.  Note that upon its return, VioSaveRedrawWait will indicate whether a screen
  17409.  save or restore should transpire. And, as with VioScrLock, the system is
  17410.  vulnerable while the save/restore thread is active, so the thread function
  17411.  must be as efficient as possible. Finally, note that a similar function,
  17412.  VioModeWait, is provided if your program only needs to save the video mode
  17413.  and not the display. VioModeWait is used in the same fashion as
  17414.  VioSaveRedrawWait.
  17415.  
  17416.  Please keep in mind that the discussion presented here pertains to graphics
  17417.  applications that need to circumvent VIO or PM. It's not an issue with
  17418.  ordinary text applications. There's nothing to stop you from using
  17419.  VioGetPhysBuf to write to the screen in text mode, if you choose. The
  17420.  efficiency you gain will, however, be offset by the code overhead needed to
  17421.  save and restore the screen. That's why the VioGetBuf/VioShowBuf combination
  17422.  is provided for text applications.
  17423.  
  17424.  
  17425.  Investigating the Debugging Registers of the Intel 386 Microprocessor
  17426.  
  17427.   Marion Hansen and Nick Stuecklen
  17428.  
  17429.  The more complex a program and the microprocessor executing it, the bigger
  17430.  the task of debugging. In very complex systems, external debuggers simply
  17431.  cannot do the whole job. Fortunately, debugging sophisticated software
  17432.  created for the 386 hardware environment is made easier by the fact that the
  17433.  Intel(R) 386 microprocessor (hereafter referred to as mp) itself provides a
  17434.  substantial set of internal debugging support features. In this article we
  17435.  will take a look at these 386 features and explore how today's
  17436.  state-of-the-art debuggers use them.
  17437.  
  17438.  In the Past
  17439.  
  17440.  In the past, PC-based debuggers could only set code breakpoints, not data
  17441.  access breakpoints, and setting those code breakpoints was restricted to
  17442.  random access memory (RAM). Data monitoring utilities could be used, but
  17443.  they merely attach themselves to the timer interrupt and periodically
  17444.  examine and display the specified region of memory. Typically, a debugger
  17445.  worked by overwriting the first byte of the specified instruction, which
  17446.  preempted the ability to set breakpoints in read only memory (ROM). If you
  17447.  wished to set a breakpoint at an instruction, an old-style debugger usually
  17448.  saved the first byte of that instruction and then overwrote the byte with a
  17449.  0CCH opcode (INT 3). When the microprocessor executed the 0CCH opcode, it
  17450.  generated an INT 3, and the debugger, which was monitoring INT 3, was
  17451.  invoked. The debugger displayed the breakpointed instruction and the
  17452.  register state, then waited for the next command.
  17453.  
  17454.  Today
  17455.  
  17456.  Fortunately the introduction of the 386 mp changed all that. With its six
  17457.  debug registers, the 386 mp provides built-in debugging support to let you
  17458.  set breakpoint addresses and define when breakpoints will occur. The four
  17459.  debug address registers are individually programmable and individually
  17460.  enabled or disabled through the debug control register. The debug status
  17461.  register maintains debug status information.
  17462.  
  17463.  In addition to supporting the usual break on instructions, the 386 mp also
  17464.  supports data access breakpoints. Data breakpoints are a very useful
  17465.  debugging tool (besides being an exceptional capability for a
  17466.  microprocessor). A data breakpoint occurs at the exact moment that data
  17467.  residing at a particular address is read or written. Using data breakpoints,
  17468.  you can immediately locate the instruction responsible for overwriting a
  17469.  data structure. The 386 mp lets you set breakpoint addresses in ROM as well
  17470.  as RAM. You can set a 386 debug exception when any of the following
  17471.  conditions occur:
  17472.  
  17473.  ■    an execution of the breakpoint instruction
  17474.  
  17475.  ■    an execution of every instruction (single-stepping)
  17476.  
  17477.  ■    an execution of any instruction at a given address
  17478.  
  17479.  ■    a read or write of a byte, word, or doubleword at any specified address
  17480.  
  17481.  ■    an attempt to change a debug register
  17482.  
  17483.  ■    a task switch to a specific task (protected mode only)
  17484.  
  17485.  Some Limitations
  17486.  
  17487.  Even with all the above capabilities, the 386 debug registers can't always
  17488.  do the job of hardware-assisted debuggers. For example, while many
  17489.  in-circuit emulators (ICEs) can maintain breakpoints across processor
  17490.  resets, a 386 mp reset clears the debug registers, effectively destroying
  17491.  any previously set breakpoints. Another limitation is that many ICEs can set
  17492.  breakpoints on I/O space accesses, but the 386 debug registers can't
  17493.  distinguish between memory and I/O space accesses. All data breakpoints are
  17494.  associated with the memory space only. You need an ICE to trap I/O space
  17495.  accesses, or you can use an I/O permission bitmap in virtual 8086 mode.
  17496.  
  17497.  You can use a hardware-assisted debugger such as an ICE to debug a debugger,
  17498.  but the only way to debug a 386-based debugger using the 386 debug registers
  17499.  is to ensure that the debugger uses only the INT 3 breakpoint. When
  17500.  triggered, the 386 debug registers generate an INT 1; therefore, a debugger
  17501.  relying only on INT 1 can debug a debugger relying only on INT 3. Further,
  17502.  because you need stable hardware to use the 386 debug registers, debugging
  17503.  in harsh, unknown system environments is easier with a full-scale ICE or
  17504.  other hardware-assisted debugger. System developers usually don't have the
  17505.  functional keyboard, display, disk, and operating system that is needed to
  17506.  load and run a debugger relying only on the 386 debug registers; they must
  17507.  rely on an ICE when debugging preliminary software and device drivers.
  17508.  
  17509.  You do, however, need more than just the 386 debug registers to create an
  17510.  acceptable software debugger. Programmers expect today's debuggers to
  17511.  provide recognition of symbolics, fast disassemblies, and the ability to
  17512.  handle multiple object module formats (OMFs). Another desirable feature is a
  17513.  Boolean comparison layer above the debug exception handler.
  17514.  
  17515.  Debuggers that take advantage of the 386 debug features are on the horizon.
  17516.  Combining the 386 debug features with what is available on today's debuggers
  17517.  will produce a powerful development tool. In the meantime, the example at
  17518.  the end of this article is part of a working program that exploits the 386
  17519.  debug features to create a useful debugging tool.
  17520.  
  17521.  Built-in Features
  17522.  
  17523.  The 386 mp provides several built-in debugging features:
  17524.  
  17525.  ■    Four breakpoints. You can specify four addresses that the CPU will
  17526.  automatically monitor.
  17527.  
  17528.  ■    Arm or disarm the breakpoints. You can selectively enable and disable
  17529.  various debug conditions that are associated with the four debug addresses.
  17530.  
  17531.  ■    Data  and  instruction breakpoints. You can set breakpoints on data
  17532.  accesses as well as on instruction executions.
  17533.  
  17534.  ■    Singlestepping. You can step through the program one instruction at a
  17535.  time.
  17536.  
  17537.  Let's look at how the 386 debug features are implemented. Along the way, we
  17538.  will develop some debugger software that will amplify these features.
  17539.  
  17540.  Debug Registers
  17541.  
  17542.  The 386 mp has six registers to control debug features. Figure 1 shows the
  17543.  format of the debug registers. You can access them by using variants of the
  17544.  MOV instruction. A debug register can be either the source or the
  17545.  destination operand. In protected mode, the MOV instructions that access the
  17546.  debug registers can only be executed at privilege level zero. Trying to read
  17547.  or write the debug registers from any other privilege level causes a general
  17548.  protection exception. Under a protected mode operating system (such as OS/2
  17549.  or XENIX(R) systems), the debug registers are considered privileged
  17550.  resources, and only privileged tasks can access them.
  17551.  
  17552.  Debug address registers (DR0-DR3). You can set a breakpoint address in
  17553.  each of the four debug address registers. Each register contains a linear
  17554.  address used to identify a breakpoint. (In contrast, addresses are presented
  17555.  as a segment/offset pair in real mode.)
  17556.  
  17557.  Debug control register (DR7). The debug control register lets you define,
  17558.  enable, and disable debug conditions. It specifies the conditions under
  17559.  which the 386 recognizes a breakpoint, the breakpoint type (local or
  17560.  global), and the breakpoint length field. The low order 8 bits of DR7 enable
  17561.  or disable the four breakpoints. Each breakpoint has 2 enable bits. The L
  17562.  bit enables local breakpoints, and the G bit enables global ones. The real
  17563.  mode debugging program shown later in this article sets and clears both
  17564.  bits.
  17565.  
  17566.  For each address in register DR0 through DR3, the corresponding fields R/W0
  17567.  through R/W3 (in register DR7) specify the type of action that can cause a
  17568.  breakpoint. The 386 interprets these bits as follows:
  17569.  
  17570.   Bits    Meaning
  17571.  
  17572.  00    break on instruction execution only
  17573.  
  17574.  01    break on data writes only
  17575.  
  17576.  10    undefined
  17577.  
  17578.  11    break on data reads and writes but not on instruction fetches
  17579.  
  17580.  The breakpoint length field (LENn) is associated primarily with data
  17581.  breakpoints. The length of a data breakpoint can be a byte, a word, or a
  17582.  doubleword. Specifying only the starting address of a data item is not
  17583.  enough to match a breakpoint condition because data items can be of three
  17584.  different lengths (8, 16, and 32 bits). The length field adds flexibility by
  17585.  selecting a range of address accesses which can trigger a breakpoint. You
  17586.  can specify the following lengths:
  17587.  
  17588.   Bits    Meaning
  17589.  
  17590.  00    1-byte length
  17591.  
  17592.  01    2-byte length (word)
  17593.  
  17594.  10    undefined
  17595.  
  17596.  11    4-byte length
  17597.  
  17598.      (doubleword)
  17599.  
  17600.  Because instruction breakpoints should uniquely specify the byte-granular
  17601.  starting address of the intended instruction, instruction breakpoints always
  17602.  specify a length field of 1 byte. If RWn is 00--an instruction
  17603.  execution breakpoint--LENn should also be 00 (1 byte). Any other length
  17604.  is undefined.
  17605.  
  17606.  When the LE (local) or GE (global) bit is set, the 386 mp slows execution so
  17607.  data breakpoints can be reported on the precise instruction that caused
  17608.  them. Keep in mind that while an instruction execution breakpoint occurs
  17609.  before the specified instruction is executed, a data access (read or write)
  17610.  breakpoint occurs after the specified data is read or written.
  17611.  
  17612.  Debug status register (DR6). The debugger uses the debug status register to
  17613.  determine what debug conditions have occurred. The 386 mp sets status bits,
  17614.  and the debugger reads them. When the microprocessor detects a debug
  17615.  exception, it sets one or more of the status register's 4 low-order bits, B0
  17616.  through B3, before entering the debug exception handler. Bn is set if the
  17617.  condition described in the address registers (DRn) and the control register
  17618.  (LENn and R/Wn) occurs.
  17619.  
  17620.  The BS bit, associated with the TF (trap flag) bit of the 386 EFLAGS
  17621.  register, is set if the debug handler is entered, since a single-step
  17622.  exception occurred. The single-step trap is the highest-priority debug
  17623.  exception. When BS is set, any of the other debug status bits may also have
  17624.  been set by the 386 mp. This means that a single-step trap can occur at the
  17625.  same time an instruction or data breakpoint occurs. The BT and BS bits are
  17626.  used only when the microprocessor is in protected mode.
  17627.  
  17628.  The BT bit is associated with the T bit (debug trap bit) of the task state
  17629.  segment, or TSS. The TSS is a data structure defined by the 386 system
  17630.  architecture; it is available only in protected mode. Each task has its own
  17631.  TSS, which holds the state of the task's virtual processor. The 386 mp sets
  17632.  the BT bit before entering the debug handler if a task switch has occurred
  17633.  and if the T bit of the new TSS is set. There is no corresponding bit in DR7
  17634.  that enables and disables this trap; the T bit of the TSS is the only
  17635.  enabling bit.
  17636.  
  17637.  The ICE-386 is Intel's in-circuit emulator for the 386 mp. When the ICE-386
  17638.  is attached, it has priority over the 386 debugger. The BD bit is set if the
  17639.  next instruction will read or write one of the debug registers and ICE-386
  17640.  is also using the debug registers.
  17641.  
  17642.  The 386 mp only clears the bits of DR6 when the microprocessor is reset. To
  17643.  avoid confusion in identifying the cause of the next debug exception, the
  17644.  debug handler should move zeros to DR6 immediately before returning.
  17645.  
  17646.  Setting Breakpoints
  17647.  
  17648.  Each of the four breakpoints  is defined by its linear address (DRn) and its
  17649.  length (LENn). The LEN field lets you specify a 1-, 2-, or 4-byte field. The
  17650.  386 mp requires that 2-byte fields be aligned on word boundaries (addresses
  17651.  that are multiples of two), and 4-byte fields must be aligned on doubleword
  17652.  boundaries (addresses that are multiples of four). You will get unexpected
  17653.  results if code or data breakpoint addresses are not properly aligned.
  17654.  
  17655.  You can set a data breakpoint for a misaligned field longer than 1 byte by
  17656.  using two or more debug address registers to hold the entire address. Each
  17657.  entry must be properly aligned, and the entries must span the length of the
  17658.  field. For example, when setting three breakpoints for a doubleword address
  17659.  that starts on an odd-byte boundary, the first address identifies the first
  17660.  byte of the doubleword, the second one identifies the next two bytes, and
  17661.  the third identifies the last byte. Figure 2 shows you how to align
  17662.  breakpoint addresses for the address of a doubleword starting on an odd-byte
  17663.  boundary.
  17664.  
  17665.  A memory access triggers a data read or write breakpoint when it occurs
  17666.  within a defined breakpoint field (as determined by a breakpoint address
  17667.  register and its corresponding LEN field). Figure 3 contains examples
  17668.  showing when breakpoints will and will not occur.
  17669.  
  17670.  Instruction  breakpoint addresses are always specified as 1 byte (LEN=00).
  17671.  Other values for instruction breakpoint addresses are undefined. The 386
  17672.  recognizes an instruction breakpoint only when the breakpoint address points
  17673.  to the first byte of an instruction. Therefore, if the instruction has
  17674.  prefixes, then the breakpoint address must point to the first prefix byte.
  17675.  
  17676.  Debug Exceptions
  17677.  
  17678.  Breakpoints set on instructions cause faults; all other debug conditions
  17679.  cause traps. (Faults break before executing the instruction at the specified
  17680.  address. Traps report a data access breakpoint after executing the
  17681.  instruction that accesses the given memory item.) The debug exception can
  17682.  report faults and traps at the same time. The following describes the four
  17683.  classes of debug exceptions.
  17684.  
  17685.  Instruction execution breakpoint. An instruction execution breakpoint is a
  17686.  fault, so the 386 mp reports an instruction execution breakpoint before it
  17687.  executes the instruction at the given address.
  17688.  
  17689.  Data access breakpoint. A data access breakpoint exception is a trap. That
  17690.  is, the processor reports a data access breakpoint after executing the
  17691.  instruction that accesses the given memory item.
  17692.  
  17693.  When using data breakpoints, you should set the DR7's LE bit, GE bit, or
  17694.  both. If either LE or GE is set, any data breakpoint trap is reported
  17695.  exactly after completion of the instruction that accessed the specified
  17696.  memory item. This exact reporting is accomplished by forcing the 386
  17697.  execution unit to wait for completion of data operand transfers before
  17698.  beginning execution of the next instruction. If neither GE nor LE is set,
  17699.  data breakpoints may not be reported until one instruction after the data is
  17700.  accessed or they may not be reported at all. This is because the 386 mp
  17701.  normally overlaps instruction execution with memory transfers to such a
  17702.  degree that execution of the next instruction may begin before memory
  17703.  transfers for the previous instruction are completed.
  17704.  
  17705.  If a debugger is creating a data write breakpoint, it should save the
  17706.  original data contents before setting the breakpoint. Because data
  17707.  breakpoints are traps, a write into a breakpoint location is completed
  17708.  before the trap condition is reported. The handler can report the saved
  17709.  (original) value after the breakpoint has been triggered. The data in the
  17710.  debug registers can be used to address the new value written by the
  17711.  instruction that triggered the breakpoint.
  17712.  
  17713.  Single-step trap. This debug condition occurs at the end of an instruction
  17714.  if the trap flag (TF) of the flag's register held the value 1 at the
  17715.  beginning of that instruction. Note that the exception does not occur at the
  17716.  end of an instruction that sets TF. For example, if POPF is used to set TF,
  17717.  a single-step trap does not occur until after the instruction that follows
  17718.  POPF.
  17719.  
  17720.  The interrupt priorities in hardware guarantee that if an external interrupt
  17721.  occurs, single stepping stops. When an external and a single-step interrupt
  17722.  occur together, the single-step interrupt is processed first. This clears
  17723.  the TF bit. After saving the return address or the switch tasks, the
  17724.  external interrupt input is examined before the first instruction of the
  17725.  single-step handler executes. If the external interrupt is still pending, it
  17726.  is then serviced. The external interrupt handler is not single stepped. In
  17727.  order to single step an external interrupt handler, single step an INT n
  17728.  instruction that refers to the interrupt handler.
  17729.  
  17730.  Task switch breakpoint. In protected mode, a breakpoint occurs after
  17731.  switching to a new task if the new TSS's T bit is set. The breakpoint occurs
  17732.  after control passes to the new task but before the first instruction is
  17733.  executed. The exception handler can detect a task switch by examining the BT
  17734.  bit of the debug status register (DR6).
  17735.  
  17736.  Interrupts
  17737.  
  17738.  Both the 386 and earlier microprocessors have two interrupt vectors
  17739.  dedicated to debugging. Interrupt 1 is reserved for the single-step
  17740.  instruction trap, interrupt 3 for instruction breakpoints. In addition, the
  17741.  386 mp generates an interrupt 1 on any debug register trigger.
  17742.  
  17743.  Interrupt 1. The handler for interrupt 1 is usually a debugger or part of a
  17744.  debugging system. Figure 4 shows the seven breakpoint conditions that can
  17745.  cause an interrupt 1. The debugger can check flags in DR6 and DR7 to
  17746.  determine what condition caused the exception and what other conditions
  17747.  might have occurred.
  17748.  
  17749.  Interrupt 3. This exception is caused by execution of the breakpoint
  17750.  instruction INT 3. Typically, a pre-386 debugger prepared a breakpoint by
  17751.  substituting the opcode of the 1-byte breakpoint instruction for the first
  17752.  opcode byte of the instruction to be trapped.
  17753.  
  17754.  Prior to the 386 machine, microprocessors used the breakpoint exception
  17755.  extensively for trapping execution of specific instructions. The 386 mp
  17756.  solves this need more conveniently by using the debug registers and
  17757.  interrupt 1. However, the breakpoint exception is still useful for debugging
  17758.  debuggers because the breakpoint exception can vector to an exception
  17759.  handler that is different from that used by the debugger. The breakpoint
  17760.  exception can also be useful when you need to set more breakpoints than
  17761.  allowed by the four debug registers.
  17762.  
  17763.  Sample Debugger Program
  17764.  
  17765.  You don't need to scrap your old-style debugger to take advantage of the
  17766.  386's built-in debugging features. With a little help from a friendly
  17767.  debugging assistant (such as the working program fragment shown later in
  17768.  this article), you can continue to use your current debugger. By enabling or
  17769.  disabling data breakpoints with a pop-up routine, the debugging assistant
  17770.  shown supplements the setting of instruction breakpoints by old-style
  17771.  debuggers. You still have all the benefits of your older debugger (symbolics
  17772.  and disassemblies, for example) plus the powerful built-in debugging
  17773.  capabilities of the 386 mp.
  17774.  
  17775.  The 386-based debugging assistant runs in the background as an adjunct to a
  17776.  traditional, pre-386 debugger. The debugging assistant is a
  17777.  terminate-and-stay-resident (TSR) program that pops up either when you press
  17778.  the SYSREQ key or when a debug exception occurs. The pop-up screen describes
  17779.  the 386 registers and identifies which of the four individually programmable
  17780.  breakpoints occurred.
  17781.  
  17782.  Our debugging assistant has several features built into it. A human
  17783.  interface displays all the registers you're interested in and lets you
  17784.  easily enter breakpoint addresses, enable/disable breakpoints, and define
  17785.  breakpoint conditions. Figure 5 shows a model of this screen, which is
  17786.  divided into the following five option fields:
  17787.  
  17788.  ■    BREAKPOINT contains the four 386 debug registers and their options.
  17789.  
  17790.  ■    COMPARE shows the Boolean comparison options that are available.
  17791.  
  17792.  ■    SPECIAL REGS contains the 386 EFLAGS register in its hexadecimal
  17793.  representation, the debug control register (DR7), and the debug status
  17794.  register (DR6).
  17795.  
  17796.  ■    REGISTER SET displays the values in the most frequently referenced 386
  17797.  registers.
  17798.  
  17799.  ■    EFLAGS  presents  the EFLAGS register decoded into an English format.
  17800.  
  17801.  You can change most of the values displayed on the screen by either editing
  17802.  or toggling each respective field.
  17803.  
  17804.  A Boolean comparison layer decides if the exception meets the criteria you
  17805.  specified. The debugging assistant has Boolean comparison logic that is
  17806.  armed and disarmed separately by toggling the switch (sw) field in COMPARE.
  17807.  You can also toggle the Boolean (bool) field in COMPARE between:
  17808.  
  17809.  <    less than
  17810.  
  17811.  < =    less than or equal to
  17812.  
  17813.  =    equal to
  17814.  
  17815.  < >    not equal to
  17816.  
  17817.  >    greater than
  17818.  
  17819.  > =    greater than or equal to
  17820.  
  17821.  The value to be used in the comparison logic is specified by editing the
  17822.  value field in COMPARE.
  17823.  
  17824.  An interrupt service routine handles exceptions. The debugging assistant
  17825.  program has two interrupt service routines (ISRs). One ISR handles the
  17826.  SYSREQ key and the other handles debug exception interrupts. Part of the
  17827.  code in Figure 6 contains an interrupt service routine.
  17828.  
  17829.  Common code is called by the interrupt service routines. In the debugging
  17830.  assistant program, both ISRs call a common block of code that copies the 386
  17831.  registers into a large data structure. The common code then passes the
  17832.  address of the data structure to the high-level language program that
  17833.  controls the human interface.
  17834.  
  17835.  After the registers are modified (through the human interface), the
  17836.  high-level language program returns control to the common code. The common
  17837.  code then copies the edited register images back into the 386 registers and
  17838.  "goes to sleep" until the next debug exception or until the SYSREQ key is
  17839.  pressed. Figure 6 contains the common code, written in assembly language.
  17840.  
  17841.  Some of the assembly language mnemonics may be unfamiliar. Because of the
  17842.  absence of 386 assemblers, we hand-assembled certain 386-specific
  17843.  instructions. Most of them were relatively easy to code because they only
  17844.  needed a prefix byte. The prefix byte specifies that the following
  17845.  instruction will use 32-bit operands, rather than 16-bit pre-386
  17846.  instructions. For example, inserting the prefix byte 066h ahead of
  17847.  
  17848.  mov ax,bx
  17849.  
  17850.  forms an instruction which the 386 mp interprets as:
  17851.  
  17852.  mov eax,ebx
  17853.  
  17854.  Figure 7 shows the flow of the debugging assistant program.
  17855.  
  17856.  A thorough understanding of the 386 debug support features presented in this
  17857.  article will provide a good starting point for tackling difficult debugging
  17858.  chores. It makes it possible to build special debugging utilities that focus
  17859.  on specific problems that might not be handled by a generic debugger. We are
  17860.  not, of course, suggesting that the reader should actually build a debugger.
  17861.  But under certain circumstances a debugging assistant such as the one
  17862.  presented here will decrease debugging time plus give you the satisfaction
  17863.  of both fully exploiting the debugging capabilities and better understanding
  17864.  the internal workings of the most advanced microprocessor on the market.
  17865.  
  17866.  
  17867.  Strategies for Building and Using OS/2 Run-Time Dynamic-Link Libraries
  17868.  
  17869.   Ross M. Greenberg
  17870.  
  17871.  Your process has died.
  17872.  
  17873.  Later, when you run the BACKTRACE function in your debug DLL, you find that
  17874.  you TOOK the ax with a strncmp but tried to THROW with a strcmp. And with a
  17875.  quick correction the bug is history.
  17876.  
  17877.  A Debug DLL
  17878.  
  17879.  What is a debug DLL anyway, and how can you create one?
  17880.  
  17881.  The Dynamic-Link Library (DLL) capabilities of OS/2 systems actually come in
  17882.  two "flavors." The first, called static linking, was described fully in
  17883.  "Design Concepts and Considerations in Building an OS/2 Dynamic-Link
  17884.  Library," MSJ (Vol. 3, No. 3). Briefly stated, the static linking
  17885.  implementation of DLLs lets an EXE function be called into memory from
  17886.  another file, a DLL.
  17887.  
  17888.  When the EXE program loads, the necessary functions are loaded into memory
  17889.  from the DLL, linkage references to the functions are resolved, and the EXE
  17890.  starts to run. Only functions referenced at link time are loaded into
  17891.  memory, and the operating system can discard and reload them as needed.
  17892.  Importantly, only one copy of any given function needs to be in memory and
  17893.  it is then shared by all sessions requiring it.
  17894.  
  17895.  There are various advantages to using the Dynamic-Link capabilities of OS/2
  17896.  systems when developing code: smaller run times; the ability to truly share
  17897.  a single copy of your developed routines between programs (when properly
  17898.  configured, only a single copy of your routines will get loaded into memory
  17899.  regardless of how many processes use them); the ability to distribute
  17900.  different versions of your program packaged with different DLLs to serve
  17901.  different markets; and so on.
  17902.  
  17903.  DLLs were such a good concept that a majority of OS/2 is, in reality, a
  17904.  collection of DLLs. Different OEMs, when porting OS/2 over to their own
  17905.  unique hardware environment, need only replace individual DLLs as they
  17906.  continue with their port effort.
  17907.  
  17908.  Each software author will most likely find a different aspect of the DLL
  17909.  concept that fits their needs; DLLs are that flexible. Although a little
  17910.  difficult to use initially, I've found that most of my OS/2 code uses DLLs
  17911.  more and more--and, more and more, it uses routines already written as
  17912.  I create my own libraries, with a resolution granularity that's definable on
  17913.  a routine-by-routine basis.
  17914.  
  17915.  However, loading a DLL like this doesn't let you load functions as you might
  17916.  wish--and all functions you reference in the link stage are loaded into
  17917.  memory. If you happen to have a very large, infrequently used function, it
  17918.  will still be read from disk, loaded into memory, and later (perhaps)
  17919.  discarded by the operating system if there's a memory crunch.
  17920.  
  17921.  Dynamic Linking
  17922.  
  17923.  OS/2 provides a second flavor to its Dynamic-Link Library Package: the
  17924.  ability to have "run-time linking." In essence, this lets you load functions
  17925.  (called procedures in DLL parlance) as needed. And when necessary, you can
  17926.  (in essence) discard them just as easily. You can even mix the two different
  17927.  types of DLLs, so that the operating system takes care of the "usual" cases
  17928.  and you take care of the unusual ones.
  17929.  
  17930.  Necessary Functions
  17931.  
  17932.  There are only five new function calls you need to learn to take advantage
  17933.  of this feature. But there are several strategies you will need to consider
  17934.  as you create and build your DLLs. The five new functions are:
  17935.  
  17936.  DosLoadModule
  17937.  
  17938.  DosGetProcAddr
  17939.  
  17940.  DosFreeModule
  17941.  
  17942.  DosGet ModHandle
  17943.  
  17944.  DosGetModName
  17945.  
  17946.  In discussing these functions I'm going to begin using the C notation
  17947.  provided in the most recent set of manuals from the Microsoft(R) Software
  17948.  Development Kit (SDK); assembly language programmers, be forewarned. The new
  17949.  definitions and typedefs allow for more machine independent code, although
  17950.  they might be a bit difficult to understand at first. A bit of sound advice
  17951.  here: it is essential that the very first thing you do is to make a complete
  17952.  hard-copy list of all the OS/2 header files. Study them now and you will
  17953.  save yourself lots of digging for the information later. Future versions of
  17954.  OS/2 systems will most likely allow the use of different addressing schemes,
  17955.  so machine independence is actually a valuable concept, even at this stage.
  17956.  
  17957.  To begin with we have DosLoadModule, defined (prototyped) in BSEDOS.H:
  17958.  
  17959.  USHORT    DosLoadModule(
  17960.             bomb_ptr,
  17961.             bomb_ptr_len,
  17962.             mod_name_ptr,
  17963.                 mod_handle_ptr)
  17964.  PSZ       bomb_ptr;
  17965.  USHORT    bomb_ptr_len;
  17966.  PSZ       mod_name_ptr;
  17967.  PHMODULE  mod_handle_ptr;
  17968.  
  17969.  The bomb_ptr field is a character array, of length bomb_ptr_len. The
  17970.  operating system will fill in this array with the null-terminated name of
  17971.  the DLL file that caused the error in loading. Normally this null-terminated
  17972.  string would be equivalent to the actual module name, mod_name_ptr. But
  17973.  since a DLL can call other DLLs, there is a possibility that some DLL
  17974.  further down the "tree" might have caused the problem.
  17975.  
  17976.  If the function returns a zero, it loaded successfully. If the module was
  17977.  already loaded by some other process, the reference count on the module is
  17978.  incremented. The module will remain loaded as long as the reference count is
  17979.  greater than zero. Only segments of the DLL marked as "preload" in the DLL's
  17980.  DEF file will be automatically loaded.
  17981.  
  17982.  The module name itself must be the name of a DLL in your LIBPATH or the
  17983.  function will fail. The module handle must be preserved for all other calls
  17984.  of the run-time load package; think of it as a file handle if you wish. Once
  17985.  the module can be referenced by a handle, routines within the DLL can now be
  17986.  accessed--if their address is known.
  17987.  
  17988.  USHORT    DosGetProcAddr(
  17989.              mod_handle,
  17990.                  function_name,
  17991.              function_ptr)
  17992.  HMODULE   mod_handle;
  17993.  PSZ       function_name;
  17994.  PPFN      function_ptr;
  17995.  
  17996.  This function will return the address in the DLL (specified in mod_handle)
  17997.  of the named function into the function pointer, function_addr. Once
  17998.  returned, it may be called via dereferencing as with any other function
  17999.  pointer. Functions with the same name can be referenced in different DLLs
  18000.  since the handle to the DLL itself would be different.
  18001.  
  18002.  The name of the function, however, can also be an ordinal number. Each
  18003.  function within a DLL can have a public name associated with it. For
  18004.  efficiency, you can remove the function names from the DLLs and only allow
  18005.  reference through the ordinal number. As an example, DOSCALLS.DLL does not
  18006.  have the function names readily available, and functions contained therein
  18007.  must be done by ordinal number.
  18008.  
  18009.  An ordinal number is of the form "#xyz"--it must start with a pound
  18010.  sign, and the remaining part of the string (that is, xyz) is the ASCII
  18011.  representation of the ordinal number of the function you wish to reference.
  18012.  
  18013.  OS/2 does not provide a means of discarding a function you no longer have
  18014.  any use for. But the operating system will discard the function if it needs
  18015.  the memory the function occupies. If you no longer reference the function,
  18016.  then it will not be reloaded into memory.
  18017.  
  18018.  On the other hand, when you no longer need a DLL module, you may discard it.
  18019.  
  18020.  USHORT    DosFreeModule(
  18021.               mod_handle)
  18022.  HMODULE   mod_handle;
  18023.  
  18024.  This will decrement the reference count of the module itself. If the
  18025.  reference count returns to zero, then all memory allocated to the module
  18026.  will be deallocated and discarded as required. Even if the reference count
  18027.  of the module is not zero, however, the local process's references to the
  18028.  DLL are no longer valid; calls to function addresses returned by
  18029.  DosGetProcAddr will fail with a protection violation.
  18030.  
  18031.  When a program exits, it returns all of its resources to the operating
  18032.  system, so it is not necessary to call DosFreeModule when you exit your
  18033.  program--although it is good programming practice.
  18034.  
  18035.  Two complementary functions exist, for determining whether the process has
  18036.  already loaded a module and for determining the name of a module based on
  18037.  its handle.
  18038.  
  18039.  USHORT    DosGetModHandle(
  18040.             mod_ptr,
  18041.                mod_handle_ptr)
  18042.  PSZ       mod_ptr;
  18043.  PHMODULE  mod_handle_ptr;
  18044.  
  18045.  This function will return the handle associated with a named DLL module.
  18046.  It's useful if you want to double-check whether a given module is already
  18047.  loaded. Since the handle itself is local to a given process, if you need to
  18048.  use this call, you should double-check your error processing--how could
  18049.  you forget a handle?
  18050.  
  18051.  There are some specific reasons for using this function  in a large program.
  18052.  Potentially, some other member of your programming team could have released
  18053.  the module, and then reloaded it later. This would usually cause it to get a
  18054.  different handle, which might cause you a problem if the handle is in a
  18055.  local, static variable. You can find a way around that problem in the debug
  18056.  routine in the sample program.
  18057.  
  18058.  DosGetModName, which is the best function, is defined as follows:
  18059.  
  18060.  USHORT    DosGetModName(
  18061.            mod_handle,
  18062.            name_len,
  18063.            name_ptr)
  18064.  HMODULE   mod_handle;
  18065.  USHORT    name_len;
  18066.  PCHAR     name_ptr;
  18067.  
  18068.  This function simply returns the DLL name associated with a given handle
  18069.  into the character array of name_ptr. If the name of the DLL can't fit into
  18070.  the character array, then an error condition is returned. This is useful in
  18071.  determining the name of a currently loaded DLL. Again, take a look at the
  18072.  debug function in the sample program for an idea of how to use it.
  18073.  
  18074.  DLLs Call DLLs
  18075.  
  18076.  One of the more interesting aspects of DLLs has largely been ignored until
  18077.  now; the ability of a DLL to utilize another DLL almost infinitely. I use
  18078.  this technique as an efficient way of dealing with two distinct DLLs
  18079.  utilized via the run-time load mechanisms.
  18080.  
  18081.  There is a decent enough reason for it. You needn't change any code in an
  18082.  applications program to take advantage of things like extended commands and
  18083.  functions; it becomes transparent where the actual DLL "lives," and the
  18084.  total separation of the two DLLs--through a third, common
  18085.  DLL--allows easy modification of one without having to recompile or
  18086.  maintain the other.
  18087.  
  18088.  FNDFILE
  18089.  
  18090.  The simple application I use to demonstrate the run-time loading
  18091.  capabilities of OS/2, FNDFILE (see Figures 1, 2, and 3), uses the OS/2
  18092.  function call, DosFindFirst. This function lets you retrieve structures
  18093.  containing information about files with names matching a given pattern. You
  18094.  can retrieve information on as many files matching the pattern as you like,
  18095.  so long as you provide a buffer large enough to hold the returned
  18096.  information.
  18097.  
  18098.  One interesting little quirk in DosFindFirst is that--although it is
  18099.  documented to fill a buffer with the contents of a structure, called
  18100.  FILEFINDBUF--it cheats a little. Basically the structure is a bunch of
  18101.  dates and times, a couple of longs (to indicate filesize), a character
  18102.  count, and an array of 13 characters, which holds the filename. The
  18103.  character array contains only enough bytes to satisfy the need for a
  18104.  null-terminated string. The next structure starts immediately after the null
  18105.  byte, so a simple pointer-to-structure increment doesn't work, since that
  18106.  increments the pointer by exactly one sizeof(FILEFINDBUF). Therefore, when
  18107.  examining the code in the show routine, please forgive the ugliness of the
  18108.  increment to the pointer.
  18109.  
  18110.  An interesting concept used in OS/2 is that of a "directory-search handle."
  18111.  This lets you consider the DosFindFirst function a resource which can be
  18112.  allocated and deallocated like any other resource. For example you could set
  18113.  up a number of initial requests to find a different matching filename
  18114.  pattern (say, the same pattern in different directories) and to then call
  18115.  functions, passing the directory-search handle as a parameter, which in turn
  18116.  call DosFindNext using those handles to find the next file on a
  18117.  pattern-by-pattern basis. Calling DosFindFirst with a directory-search
  18118.  handle already in use causes closing and then reuse of that handle.
  18119.  
  18120.  Other parameters in the DosFindFirst function let you include matching
  18121.  "special" files in your request, including directories, hidden files,
  18122.  read-only files, etc. By examining the returned attribute for each of the
  18123.  files, you can determine what type of special file you're currently
  18124.  examining.
  18125.  
  18126.  The DosFindFirst function is useful enough that most of us will have some
  18127.  need of it. Yet it's also sufficiently complex that we'll all make mistakes
  18128.  when we first use it. Additionally it makes a perfect test case for a debug
  18129.  DLL.
  18130.  
  18131.  The structure of the FNDFILE program is a bit convoluted, so some
  18132.  explanation is in order. The main routine lets you enter the parameters of
  18133.  your choice via a simple keyword parsing if-then-else clause. If you enter
  18134.  the "GO" command, you call the do_find function. Other "action" keywords let
  18135.  you turn debugging on or off, show your current parameter list, and show the
  18136.  current return buffer contents.
  18137.  
  18138.  The do_find function is called with parameters passed exactly as
  18139.  DosFindFirst expects them; it makes it easier to do the call later. When the
  18140.  find program is first invoked, the debug function is called with the flag
  18141.  turned off, and debug itself (in FNDFILE3) loads the normal DosFindFirst
  18142.  function from the appropriate DLL, DOSCALLS.
  18143.  
  18144.  You cannot, however, just load the DosFindFirst function as you'd wish. The
  18145.  function name isn't immediately accessible from the DOSCALLS library. Since
  18146.  the name is "private," I was forced to do a hex dump of the OS2.LIB file (or
  18147.  the DOSCALLS.LIB file, depending on your version of OS/2) and to find the
  18148.  so-called "ordinal number" of the DosFindFirst function.
  18149.  
  18150.  An ordinal number is basically a simple numeric representation of the
  18151.  function number itself in the library, and is a more efficient way of
  18152.  calling important functions than forcing the kernel to do a strncmp on a
  18153.  function name.
  18154.  
  18155.  The hex dump (see Figure 4) shows 64 as the ordinal number for DosFindFirst.
  18156.  To indicate that you're using an ordinal number, you must specify the number
  18157.  as an ASCII null-terminated string, with a '#' prefixed to it as the
  18158.  function name in the DosLoadProcAddr function.
  18159.  
  18160.  Before you can call the DosLoadProcAddr function, the appropriate DLL must
  18161.  be opened up and a handle, returned by the operating system, preserved for
  18162.  future reference. Use of the DosGetModHandle checks whether the program has
  18163.  already loaded the DLL; if so, DosLoadModule, the function which returns the
  18164.  handle address, is not called. If, however, the DLL is not open, this
  18165.  implies that the other DLL is open (for all calls to debug but the first).
  18166.  Therefore a call is made to DosGetModName, simply to show the name of the
  18167.  DLL already loaded, and the open DLL is closed via the DosFreeModule call.
  18168.  
  18169.  The DosLoadProcAddr function returns the actual address of the requested
  18170.  function, whether called with a function name (which should be in uppercase)
  18171.  or an ASCII-based ordinal number. Simply by calling this function via the
  18172.  normal C mechanisms, you can call any one of a number of functions, as long
  18173.  as the parameters match.
  18174.  
  18175.  One of the functions with matching parameters is the do_list function. This
  18176.  is in FNDFILE2 (see Figure 2). This function simply prints out all of your
  18177.  parameters, saving the ones the call might change, then calls the real
  18178.  DosFindFirst function. This implies that the function is already loaded when
  18179.  you first invoke the FNDFILE program. If you want to avoid this
  18180.  "preloading," you can also have the do_list function call all the
  18181.  appropriate run-time load functions for its own copy of the DosFindFirst
  18182.  pointer--but this is only demonstration code, in any case.
  18183.  
  18184.  The call to DosFindFirst is done simply to get back the status code, which
  18185.  is expected and will be processed by the do_find function. The number of
  18186.  files to be found on subsequent calls is reset, as is the original handle.
  18187.  
  18188.  The important thing here to realize is that the do_find function calls
  18189.  either do_list or DosFindFirst without really knowing which. So in a
  18190.  transparent manner, you can create your program with full run-time loading
  18191.  of your difficult functions, debug them completely, then let the actual
  18192.  programming effort continue.
  18193.  
  18194.  An obvious extension of this technique would let you provide your end-users
  18195.  with a full debug DLL of all your functions and all the OS/2 system calls
  18196.  you use. Then when you get that inevitable tech support phone call, you can
  18197.  just have the user turn on debug mode and modem you a copy of the voluminous
  18198.  output the debug DLL produces.
  18199.  
  18200.  Run-time loading is one of the treasures in OS/2 which, with enough
  18201.  exploration, you'll find extraordinarily valuable as you produce some of the
  18202.  larger programs OS/2 makes possible. In fact, you can prompt for a DLL to be
  18203.  loaded, and load it on the spot! The name doesn't have to be in the code.
  18204.  Even the simple debug facility demonstrated here dwarfs the capabilities of
  18205.  other debugging methods in other operating systems.
  18206.  
  18207.  Figure 1
  18208.  
  18209.  [FNDFILE.C]
  18210.  
  18211.  #define    INCL_DOS
  18212.  #define    INCL_ERRORS
  18213.  
  18214.  #include    <os2.h>
  18215.  #include    <stdio.h>
  18216.  #include    <stdlib.h>
  18217.  #include    <string.h>
  18218.  
  18219.  
  18220.  USHORT    far    _loadds    pascal debug(USHORT);
  18221.  USHORT    far    _loadds pascal do_find(PSZ, PHDIR, USHORT,
  18222.                       PFILEFINDBUF, USHORT, PUSHORT, ULONG);
  18223.  USHORT    far    _loadds pascal do_list(PSZ, PHDIR, USHORT,
  18224.                       PFILEFINDBUF, USHORT, PUSHORT, ULONG);
  18225.  
  18226.  void    do_usage(void);
  18227.  void    main(void);
  18228.  
  18229.  void
  18230.  show(PFILEFINDBUF f_ptr, USHORT cnt)
  18231.  {
  18232.      while (cnt--)
  18233.      {
  18234.          printf("Len is: %2d. Filename is:%s\n",
  18235.                  (int)(f_ptr->cchName), f_ptr->achName );
  18236.          f_ptr = (PFILEFINDBUF) ((char far *)f_ptr + 24 +
  18237.                  (int)(f_ptr->cchName));
  18238.      }
  18239.  }
  18240.  
  18241.  void
  18242.  do_usage()
  18243.  {
  18244.   printf("\n\nexit        - to exit\n");
  18245.   printf("file=<filename> - enter name of message file\n");
  18246.   printf("hand=<val>      - 1 = default, ffff = create new\handle\n");
  18247.   printf("attrb=<attrb>   - Attributes: see pg 98 of Ref Manual\n");
  18248.   printf("len=<buf_len>   - length of output buffer to\
  18249.                             use.Allocated\n");
  18250.   printf("cnt=<cnt>       - Maximum number of files to return\n");
  18251.   printf("debug=on|off    - turn debug mode on or off\n");
  18252.   printf("show            - show buffer contents...\n");
  18253.   printf("list            - show current parameter settings\n");
  18254.   printf("go              - call DosFindFirst()\n");
  18255.  }
  18256.  
  18257.  void
  18258.  main()
  18259.  {
  18260.  CHAR        tmp_buf[256];
  18261.  CHAR        name[64];
  18262.  USHORT      max_len = 0;
  18263.  char        *buf_ptr = (char *)NULL;
  18264.  HDIR        handle = 1;
  18265.  USHORT      attrb = 0;
  18266.  USHORT      num_files = 0;
  18267.  
  18268.      if(!debug(FALSE))
  18269.      {
  18270.          printf("Error in initial call to debug\n");
  18271.          exit(1);
  18272.      }
  18273.      *name = (CHAR)NULL;
  18274.      while(TRUE)
  18275.      {
  18276.          printf(">");
  18277.          gets(tmp_buf);
  18278.          strlwr(tmp_buf);
  18279.          if(!strncmp(tmp_buf, "exit", 4))
  18280.              exit(1);
  18281.          else
  18282.          if(!strncmp(tmp_buf, "file=", 5))
  18283.              strcpy(name, tmp_buf + 5);
  18284.          else
  18285.          if(!strncmp(tmp_buf, "len=", 4))
  18286.          {
  18287.              if(buf_ptr)
  18288.                  free(buf_ptr);
  18289.              max_len = atoi(tmp_buf + 4);
  18290.              buf_ptr = calloc(max_len, 1);
  18291.          }
  18292.          else
  18293.          if(!strncmp(tmp_buf, "cnt=", 4))
  18294.              num_files = atoi(tmp_buf + 4);
  18295.          else
  18296.          if(!strncmp(tmp_buf, "hand=", 5))
  18297.              sscanf(tmp_buf + 5, "%x", &handle);
  18298.          else
  18299.          if(!strncmp(tmp_buf, "attrb=", 6))
  18300.              sscanf(tmp_buf + 6, "%x", &attrb);
  18301.          else
  18302.          if(!strncmp(tmp_buf, "list", 4))
  18303.              do_list(name, (PHDIR)&handle, attrb,
  18304.              (PFILEFINDBUF)buf_ptr, max_len,
  18305.              (PUSHORT)&num_files, (ULONG)0);
  18306.          else
  18307.          if(!strncmp(tmp_buf, "debug=", 6))
  18308.          {
  18309.              if(!debug(!strncmp(tmp_buf + 6, "on", 2)))
  18310.              {
  18311.                  printf("Error in subsequent call to debug\n");
  18312.                  exit(1);
  18313.              }
  18314.          }
  18315.          else
  18316.          if(!strncmp(tmp_buf, "show", 4))
  18317.              show((PFILEFINDBUF)buf_ptr, num_files);
  18318.          else
  18319.          if(!strncmp(tmp_buf, "go", 2))
  18320.              do_find(name, (PHDIR)&handle, attrb,
  18321.              (PFILEFINDBUF)buf_ptr, max_len,
  18322.              (PUSHORT)&num_files, (ULONG)0);
  18323.          else
  18324.          if(*tmp_buf == '?')
  18325.              do_usage();
  18326.          else
  18327.              printf("?Huh?\n");
  18328.      }
  18329.  
  18330.  }
  18331.  
  18332.  [FNDFILE.DEF]
  18333.  
  18334.  IMPORTS FNDFILE3.do_find
  18335.  IMPORTS FNDFILE3.debug
  18336.  IMPORTS FNDFILE2.do_list
  18337.  
  18338.  
  18339.  
  18340.  Figure 2
  18341.  
  18342.  [FNDFILE2.C]
  18343.  
  18344.  #define    INCL_BASE
  18345.  #define    INCL_ERRORS
  18346.  #define    INCL_DOS
  18347.  
  18348.  #include    <os2.h>
  18349.  #include    <stdio.h>
  18350.  #include    <stdlib.h>
  18351.  #include    <string.h>
  18352.  
  18353.  void show(PFILEFINDBUF f_ptr, USHORT cnt);
  18354.  
  18355.  USHORT far _loadds pascal do_list(PSZ name_ptr, PHDIR
  18356.                              hptr, USHORT attrb,
  18357.                              PFILEFINDBUF
  18358.                              buf_ptr,
  18359.                              USHORT buf_len,
  18360.                              PUSHORT num_ptr,
  18361.                              ULONG reserved)
  18362.  {
  18363.  USHORT    stat;
  18364.  HDIR      save_handle = *hptr;
  18365.  USHORT    save_num = *num_ptr;
  18366.  
  18367.      printf("File name: %s\n", name_ptr);
  18368.      printf("Buffer address is %lx\n", buf_ptr);
  18369.      printf("Buffer length: %d\n", buf_len);
  18370.      printf("File count: %d, address: %ld\n",
  18371.              *num_ptr, num_ptr);
  18372.      printf("Handle is: %d\n", *hptr);
  18373.      printf("Attribute: %d\n", attrb);
  18374.  
  18375.      stat = DosFindFirst(name_ptr, hptr, attrb, buf_ptr,
  18376.                      buf_len, num_ptr, reserved);
  18377.      printf("Return status was:%d, cnt was:%d\n", stat,
  18378.               *num_ptr);
  18379.      *hptr = save_handle;
  18380.      *num_ptr = save_num;
  18381.      return(stat);
  18382.  }
  18383.  
  18384.  [FNDFILE2.DEF]
  18385.  
  18386.  LIBRARY FNDFILE2
  18387.  EXPORTS do_list
  18388.  
  18389.  
  18390.  
  18391.  Figure 3
  18392.  
  18393.  [FNDFILE3.C]
  18394.  
  18395.  #define     INCL_BASE
  18396.  #define     INCL_ERRORS
  18397.  #define     INCL_DOS
  18398.  
  18399.  #include    <os2.h>
  18400.  #include    <stdio.h>
  18401.  #include    <stdlib.h>
  18402.  #include    <string.h>
  18403.  
  18404.  #define    FAIL_BUF_LEN    128
  18405.  PSZ    mod_name[] = {"doscalls", "fndfile2"};
  18406.  PSZ    routine_name[] = {"#64", "DO_LIST"};
  18407.  
  18408.  USHORT    (pascal far *routine)(PSZ, PHDIR, USHORT, PFILEFINDBUF, USHORT,
  18409.  PUSHORT, ULONG);
  18410.  
  18411.  USHORT    far    _loadds pascal do_find(PSZ name_ptr, PHDIR
  18412.                              hptr, USHORT attrb,
  18413.                              PFILEFINDBUF buf_ptr,
  18414.                              USHORT buf_len,
  18415.                              PUSHORT num_ptr,
  18416.                              ULONG reserved)
  18417.  {
  18418.  USHORT    stat;
  18419.  
  18420.      if(!(stat = routine(name_ptr, hptr, attrb, buf_ptr,
  18421.                      buf_len, num_ptr, reserved)))
  18422.      {
  18423.          printf("Good return. Files found = %d\n", *num_ptr);
  18424.      }
  18425.      else
  18426.      {
  18427.          switch(stat)
  18428.          {
  18429.          case  ERROR_BUFFER_OVERFLOW:
  18430.                  printf("Buffer Overflow - Increase Buffer Size\n");
  18431.                  break;
  18432.  
  18433.          case  ERROR_DRIVE_LOCKED:
  18434.                  printf("Drive Locked\n");
  18435.                  break;
  18436.  
  18437.          case  ERROR_FILE_NOT_FOUND:
  18438.                  printf("File: %s not found\n", name_ptr);
  18439.                  break;
  18440.  
  18441.          case  ERROR_INVALID_HANDLE:
  18442.                  printf("Invalid handle: %d\n", *hptr);
  18443.                  break;
  18444.  
  18445.          case  ERROR_INVALID_PARAMETER:
  18446.                  printf("Invalid Parameter\n");
  18447.                  break;
  18448.  
  18449.          case  ERROR_NO_MORE_FILES:
  18450.                  printf("Ran out of files\n");
  18451.                  break;
  18452.  
  18453.          case  ERROR_NO_MORE_SEARCH_HANDLES:
  18454.                  printf("Can;t allocate another Search Handle\n");
  18455.                  break;
  18456.  
  18457.          case  ERROR_NOT_DOS_DISK:
  18458.                  printf("Not a DOS Disk\n");
  18459.                  break;
  18460.  
  18461.          case  ERROR_PATH_NOT_FOUND:
  18462.                  printf("I can't locate that Path\n");
  18463.                  break;
  18464.  
  18465.          default:
  18466.                  printf("Unknown error in FindFirst: %d\n", stat);
  18467.                  break;
  18468.          }
  18469.      }
  18470.      return(stat);
  18471.  }
  18472.  
  18473.  
  18474.  far _loadds pascal debug(USHORT debug_flag)
  18475.  {
  18476.  USHORT    stat;
  18477.  CHAR      fail_buf[FAIL_BUF_LEN];
  18478.  static    HMODULE    handle = 0;
  18479.  HMODULE   tmp_handle;
  18480.  CHAR      tmp_buf[128];
  18481.  
  18482.      printf("Debug is: %s\n", debug_flag ? "On" : "Off");
  18483.  
  18484.  
  18485.      /* already a DLL loaded? */
  18486.  
  18487.      if(handle)
  18488.      {
  18489.          /* some DLL already loaded. Requested DLL? */
  18490.  
  18491.          stat = DosGetModHandle(mod_name[debug_flag],
  18492.                              &tmp_handle);
  18493.  
  18494.          /* if error, or a handle mismatch, then it isn't
  18495.           * the requested DLL */
  18496.  
  18497.          if(stat || tmp_handle != handle)
  18498.          {
  18499.              /* Get name of the DLL currently loaded */
  18500.  
  18501.              if(stat = DosGetModName(handle, 128,
  18502.                                  tmp_buf))
  18503.              {
  18504.                  printf("Couldn't retrieve loaded DLL Name. Error
  18505.                  code is: %d\n", stat);
  18506.                  return(FALSE);
  18507.              }
  18508.              else
  18509.                  printf("Currently Loaded DLL is: %s\n",
  18510.                          tmp_buf);
  18511.  
  18512.              /* free the already loaded module, whatever
  18513.               * it is */
  18514.  
  18515.              DosFreeModule(handle);
  18516.          }
  18517.          else
  18518.          {
  18519.              /* current handle is for requested DLL.
  18520.               * Simply return */
  18521.              printf("DLL (%s) already loaded\n",
  18522.                      mod_name[debug_flag]);
  18523.              return(TRUE);
  18524.          }
  18525.  
  18526.      }
  18527.  
  18528.      /* wrong DLL is now freed */
  18529.      /* try to load the requested DLL, and get the entry
  18530.       * points */
  18531.  
  18532.      if(stat = DosLoadModule(fail_buf, FAIL_BUF_LEN,
  18533.                          mod_name[debug_flag],
  18534.                          &handle))
  18535.      {
  18536.          printf("Couldn't load: %s (stat is :%x)\n",
  18537.                  mod_name[debug_flag], stat);
  18538.          printf("DLL problem was in: %s\n", fail_buf);
  18539.          return(FALSE);
  18540.      }
  18541.      else
  18542.          printf("Module handle is: %d\n", handle);
  18543.  
  18544.      /* Now get the entry point for the requested routine */
  18545.  
  18546.      if    (stat = DosGetProcAddr(handle,
  18547.           routine_name[debug_flag], &routine))
  18548.      {
  18549.          printf("Couldn't get routine: %s (stat is :%d)\n",
  18550.                  routine_name[debug_flag], stat);
  18551.          return(FALSE);
  18552.      }
  18553.      else
  18554.          printf("Routine address is: %lx\n", routine);
  18555.  
  18556.      /* module loaded, entry point returned, so we return */
  18557.      return(TRUE);
  18558.  }
  18559.  
  18560.  [FNDFILE3.DEF]
  18561.  
  18562.  LIBRARY    FNDFILE3
  18563.  EXPORTS    do_find
  18564.  EXPORTS    debug
  18565.  IMPORTS    FNDFILE2.do_list
  18566.  
  18567.  
  18568.  
  18569.  Figure 4
  18570.  
  18571.  Partial Dump of \PMSDK\LIB\OS2.LIB
  18572.  
  18573.  0FC0:   00 00 8A 02 00 00 00 00 00 00 00 00 00 00 00 00   ................
  18574.  0FD0:   80 0F 00 0D 44 4F 53 43 41 4C 4C 53 30 30 30 36   ....DOSCALLS0006
  18575.  0FE0:   33 16 88 1D 00 00 A0 01 01 0C 44 4F 53 46 49 4E   3.........DOSFIN
  18576.  0FF0:   44 46 49 52 53 54 08 44 4F 53 43 41 4C 4C 53 40   DFIRST.DOSCALLS@
  18577.  1000:   00 00 8A 02 00 00 00 00 00 00 00 00 00 00 00 00   ................
  18578.  1010:   80 0F 00 0D 44 4F 53 43 41 4C 4C 53 30 30 30 36   ....DOSCALLS0006
  18579.  1020:   34 15 88 1C 00 00 A0 01 01 0B 44 4F 53 46 49 4E   4.........DOSFIN
  18580.  1030:   44 4E 45 58 54 08 44 4F 53 43 41 4C 4C 53 41 00   DNEXT.DOSCALLSA.
  18581.  1040:   00 8A 02 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
  18582.  
  18583.  
  18584.  
  18585.  How the 8259A Programmable Interrupt Controller Manages External I/O Devices
  18586.  
  18587.  
  18588.  
  18589.  Jim Kyle and Chip Rabinowitz
  18590.  
  18591.  Unlike software interrupts, which are service requests initiated by a
  18592.  program, hardware interrupts occur in response to electrical signals
  18593.  received from a peripheral device such as a serial port or a disk
  18594.  controller, or they are generated internally by the microprocessor itself.
  18595.  Hardware interrupts, whether external or internal to the microprocessor, are
  18596.  given prioritized servicing by the Intel(R) CPU architecture.
  18597.  
  18598.  The 8086 family of microprocessors (which includes the 8088, 8086, 80186,
  18599.  80286, and 80386) reserves the first 1024 bytes of memory (addresses
  18600.  0000:0000H through 0000:03FFH) for a table of 256 interrupt vectors, each a
  18601.  4-byte far pointer to a specific interrupt service routine (ISR) that is
  18602.  carried out when the corresponding interrupt is processed. The design of the
  18603.  8086 family requires certain of these interrupt vectors to be used for
  18604.  specific functions (see Figure 1). Although Intel actually reserves the
  18605.  first 32 interrupts, IBM, in the original PC, redefined usage of Interrupts
  18606.  05H to 1FH. Most, but not all, of these reserved vectors are used by
  18607.  software, rather than hardware, interrupts; the redefined IBM uses are
  18608.  listed in Figure 2.
  18609.  
  18610.  Nestled in the middle of Figure 2 are the eight hardware interrupt vectors
  18611.  (08-0FH) IBM implemented in the original PC design. These eight vectors
  18612.  provide the maskable interrupts for the IBM(R) PC-family and close
  18613.  compatibles. Additional IRQ lines built into the IBM PC/AT(R) are discussed
  18614.  under The IRQ Levels below.
  18615.  
  18616.  The conflicting uses of the interrupts listed in Figures 1 and 2 have
  18617.  created compatibility problems as the 8086 family of microprocessors has
  18618.  developed. For complete compatibility with IBM equipment, the IBM usage must
  18619.  be followed even when it conflicts with the chip design. For example, a
  18620.  BOUND error occurs if an array index exceeds the specified upper and lower
  18621.  limits (bounds) of the array, causing an Interrupt 05H to be generated. But
  18622.  the 80286 processor used in all AT-class computers will, if a BOUND error
  18623.  occurs, send the contents of the display to the printer, because IBM uses
  18624.  Interrupt 05H for the Print Screen function.
  18625.  
  18626.  Hardware Interrupt Categories
  18627.  
  18628.  The 8086 family of microprocessors can handle three types of hardware
  18629.  interrupts. First are the internal, microprocessor-generated exception
  18630.  interrupts (refer to Figure 1). Second is the nonmaskable interrupt, or NMI
  18631.  (Interrupt 02H), which is generated when the NMI line (pin 17 on the 8088
  18632.  and 8086, pin 59 on the 80286, pin B8 on the 80386) goes high (active). In
  18633.  the IBM PC family (except the PCjr and the Convertible), the nonmaskable
  18634.  interrupt is designated for memory parity errors. Third are the maskable
  18635.  interrupts, which are usually generated by external devices.
  18636.  
  18637.  Maskable interrupts are sent to the main processor through a chip called the
  18638.  8259A Programmable Interrupt Controller (PIC). When it receives an interrupt
  18639.  request, the PIC signals the microprocessor that an interrupt needs service
  18640.  by driving the interrupt request (INTR) line of the main processor to high
  18641.  voltage level. This article focuses on the maskable interrupts and the 8259A
  18642.  because it is through the PIC that external I/O devices (disk drives, serial
  18643.  communication ports, and so forth) gain access to the interrupt system.
  18644.  
  18645.  Interrupt Priorities
  18646.  
  18647.  The Intel microprocessors have a built-in priority system for handling
  18648.  interrupts that occur simultaneously. Priority goes to the internal
  18649.  instruction exception interrupts, such as Divide by Zero and Invalid Opcode,
  18650.  because priority is determined by the interrupt number: Interrupt 00H takes
  18651.  priority over all others, whereas the last possible interrupt, 0FFH, would,
  18652.  if present, never be allowed to break in while another interrupt was being
  18653.  serviced. However, if interrupt service is enabled (the microprocessor's
  18654.  interrupt flag is set), any hardware interrupt takes priority over any
  18655.  software interrupt (INT instruction).
  18656.  
  18657.  The priority sequencing by interrupt number must not be confused with the
  18658.  priority resolution performed by hardware external to the microprocessor.
  18659.  The numeric priority discussed here applies only to interrupts generated
  18660.  within the 8086 family of microprocessor chips and is totally independent of
  18661.  system interrupt priorities established for components external to the
  18662.  microprocessor itself.
  18663.  
  18664.  Interrupt Service Routines
  18665.  
  18666.  For the most part, programmers need not write hardware-specific program
  18667.  routines to service the hardware interrupts. The IBM PC BIOS routines,
  18668.  together with MS-DOS services, are usually sufficient. In some cases,
  18669.  however, the MS-DOS operating system and the ROM BIOS do not provide enough
  18670.  assistance to ensure adequate performance of a program. Most notable in this
  18671.  category is communications software, for which programmers usually must
  18672.  access the 8259A and the 8250 Universal Asynchronous Receiver and
  18673.  Transmitter (UART) directly.
  18674.  
  18675.  Two major characteristics distinguish maskable interrupts from all other
  18676.  events that can occur in the system: they are totally unpredictable, and
  18677.  they are highly volatile. In general, a hardware interrupt occurs when a
  18678.  peripheral device requires the complete attention of the system and data
  18679.  will be irretrievably lost unless the system responds rapidly.
  18680.  
  18681.  All things are relative, however, and this is especially true of the speed
  18682.  required to service an interrupt request. For example, assume that two
  18683.  interrupt requests occur at essentially the same time. One is from a serial
  18684.  communications port receiving data at 300 bps; the other is from a serial
  18685.  port receiving data at 9600 bps. Data from the first serial port will not
  18686.  change for at least 30 milliseconds, but the second serial port must be
  18687.  serviced within one millisecond to avoid data loss.
  18688.  
  18689.  Unpredictability
  18690.  
  18691.  Because maskable interrupts generally originate in response to external
  18692.  physical events, such as the receipt of a byte of data over a communications
  18693.  line, the exact time at which such an interrupt will occur cannot be
  18694.  predicted. Even the timer interrupt request, which by default occurs
  18695.  approximately 18.2 times per second, cannot be predicted by any program that
  18696.  happens to be executing when the interrupt request occurs.
  18697.  
  18698.  Because of this unpredictability, the system must, if it allows any
  18699.  interrupts to be recognized, be prepared to service all maskable interrupt
  18700.  requests. Conversely, if interrupts cannot be serviced, they must all be
  18701.  disabled. The 8086 family of microprocessors provides the Set Interrupt Flag
  18702.  (STI) instruction to enable maskable interrupt response and the Clear
  18703.  Interrupt Flag (CLI) instruction to disable it. The interrupt flag is also
  18704.  cleared automatically when a hardware interrupt response begins; the
  18705.  interrupt handler should execute STI as quickly as possible to allow higher
  18706.  priority interrupts to be serviced.
  18707.  
  18708.  Volatility
  18709.  
  18710.  As noted earlier, a maskable interrupt request must normally be serviced
  18711.  immediately to prevent loss of data, but the concept of immediacy is
  18712.  relative to the data transfer rate of the device requesting the interrupt.
  18713.  The rule is that the currently available unit of data must be processed (at
  18714.  least to the point of being stored in a buffer) before the next such item
  18715.  can arrive. Except for such devices as disk drives, which always require
  18716.  immediate response, interrupts for devices that receive data are normally
  18717.  much more critical than interrupts for devices that transmit data.
  18718.  
  18719.  The problems imposed by data volatility during hardware interrupt service
  18720.  are solved by establishing service priorities for interrupts generated
  18721.  outside the microprocessor chip itself. Devices with the slowest transfer
  18722.  rates are assigned lower interrupt service priorities, and the most
  18723.  time-critical devices are assigned the highest priority of interrupt
  18724.  service.
  18725.  
  18726.  Maskable Interrupts
  18727.  
  18728.  The microprocessor handles all interrupts (maskable, nonmaskable, and
  18729.  software) by pushing the contents of the flags register onto the stack,
  18730.  disabling the interrupt flag, and pushing the current contents of the CS:IP
  18731.  registers onto the stack.
  18732.  
  18733.  The microprocessor then takes the interrupt number from the data bus,
  18734.  multiplies it by 4 (the size of each vector in bytes), and uses the result
  18735.  as an offset into the interrupt vector table located in the bottom 1Kb
  18736.  (segment 0000H) of system RAM. The 4-byte address at that location is then
  18737.  used as the new CS:IP value (see Figure 3).
  18738.  
  18739.  External devices are assigned dedicated interrupt request lines (IRQs)
  18740.  associated with the 8259A. See the subsection titled "The IRQ Levels" below.
  18741.  When a device requires attention, it sends a signal to the PIC via its IRQ
  18742.  line. The PIC, which functions as an "executive secretary" for the external
  18743.  devices, operates as shown in Figure 4. It evaluates the service request
  18744.  and, if appropriate, causes the microprocessor's INTR line to go high. The
  18745.  microprocessor then checks whether interrupts are enabled, that is, whether
  18746.  the interrupt flag is set. If they are, the flags are pushed onto the stack,
  18747.  the interrupt flag is disabled, and CS:IP is pushed onto the stack.
  18748.  
  18749.  The microprocessor acknowledges the interrupt request by signaling the 8259A
  18750.  via the interrupt acknowledge (INTA) line. The 8259A then places the
  18751.  interrupt number on the data bus. The microprocessor gets the interrupt
  18752.  number from the data bus and services the interrupt. Before issuing the IRET
  18753.  instruction, the interrupt service routine must issue an end-of-interrupt
  18754.  (EOI) sequence to the 8259A so that other interrupts can be processed. This
  18755.  is done by sending 20H to port 20H. (The similarity of numbers is pure
  18756.  coincidence.)
  18757.  
  18758.  The 8259A (see Figure 5) has a number of internal components, many of them
  18759.  under software control. Only the default settings for the IBM PC family are
  18760.  covered here.
  18761.  
  18762.  Three registers influence the servicing of maskable interrupts: the
  18763.  interrupt request register (IRR), the in-service register (ISR), and the
  18764.  interrupt mask register (IMR).
  18765.  
  18766.  The IRR is used to keep track of the devices requesting attention. When a
  18767.  device causes its IRQ line to go high, to signal    the 8259A that it needs
  18768.  service, a bit is set in the IRR that corresponds to the interrupt level of
  18769.  the device.
  18770.  
  18771.  The ISR specifies the interrupt levels that are currently being serviced; an
  18772.  ISR bit is set when an interrupt has been acknowledged by the CPU (via INTA)
  18773.  and the interrupt number has been placed on the data bus. The ISR bit
  18774.  associated with a particular IRQ remains set until an EOI sequence is
  18775.  received.
  18776.  
  18777.  The IMR is a read/write register (at port 21H) that masks (disables)
  18778.  specific interrupts. When a bit is set in this register, the corresponding
  18779.  IRQ line is masked and no servicing for it is performed until the bit is
  18780.  cleared. Thus, a particular IRQ can be disabled while all others continue to
  18781.  be serviced.
  18782.  
  18783.  The fourth major block in Figure 5, labeled Priority resolver, is a complex
  18784.  logical circuit that forms the heart of the 8259A. This component combines
  18785.  the statuses of the IMR, the ISR, and the IRR to determine which, if any,
  18786.  pending interrupt request should be serviced and then causes the
  18787.  microprocessor's INTR line to go high. The priority resolver can be
  18788.  programmed in a number of modes, although only the mode used in the IBM PC
  18789.  and close compatibles is described here.
  18790.  
  18791.  The IRQ Levels
  18792.  
  18793.  When two or more unserviced hardware interrupts are pending, the 8259A
  18794.  determines which should be serviced first. The standard mode of operation
  18795.  for the PIC is the fully nested mode, in which IRQ lines are prioritized in
  18796.  a fixed sequence. Only IRQ lines with higher priority than the one currently
  18797.  being serviced are permitted to generate new interrupts.
  18798.  
  18799.  The highest priority is IRQ0, and the lowest is IRQ7. Thus, if an Interrupt
  18800.  09H (signaled by IRQ1) is being serviced, only an Interrupt 08H (signaled by
  18801.  IRQ0) can break in. All other interrupt requests are delayed until the
  18802.  Interrupt 09H service routine is completed and has issued an EOI sequence.
  18803.  
  18804.  Eight-level Designs
  18805.  
  18806.  The IBM PC and PC/XT (and port-compatible computers) have eight IRQ lines to
  18807.  the PIC chip--IRQ0 through IRQ7. These lines are mapped into interrupt
  18808.  vectors for Interrupts 08H through 0FH (that is, 8 + IRQ level). These eight
  18809.  IRQ lines and their associated interrupts are listed in Figure 6.
  18810.  
  18811.  Sixteen-level Designs
  18812.  
  18813.  In the IBM PC/AT, 8 more IRQ levels have been added by using a second 8259A
  18814.  PIC (the "slave") and a cascade effect, which gives 16 priority levels.
  18815.  
  18816.  The cascade effect is accomplished by connecting the INT line of the slave
  18817.  to the IRQ2 line of the first, or master, 8259A instead of to the
  18818.  microprocessor. When a device connected to one of the slave's IRQ lines
  18819.  makes an interrupt request, the INT line of the slave goes high and causes
  18820.  the IRQ2 line of the master 8259A to go high, which, in turn, causes the INT
  18821.  line of the master to go high and thus interrupts the microprocessor.
  18822.  
  18823.  The microprocessor, ignorant of the second 8259A's presence, simply
  18824.  generates an interrupt acknowledge signal on receipt of the interrupt from
  18825.  the master 8259A. This signal initializes both 8259A and also causes the
  18826.  master to turn control over to the slave. The slave then completes the
  18827.  interrupt request.
  18828.  
  18829.  On the IBM PC/AT, the eight additional IRQ lines are mapped to Interrupts
  18830.  70H through 77H (see Figure 7). Because the eight additional lines are
  18831.  effectively connected to the master 8259A's IRQ2 line, they take priority
  18832.  over the master's IRQ3 through IRQ7 events. The cascade effect is
  18833.  graphically represented in Figure 8.
  18834.  
  18835.  Programming for the Hardware Interrupts
  18836.  
  18837.  Any program that modifies an interrupt vector must restore the vector to its
  18838.  original condition before returning control to DOS (or to its parent
  18839.  process). Any program that totally replaces an existing hardware interrupt
  18840.  handler with one of its own must perform all the handshaking and terminating
  18841.  actions of the original: reenable interrupt service, signal EOI to the
  18842.  interrupt controller, and so forth. Failure to follow these rules has led to
  18843.  many hours of programmer frustration.
  18844.  
  18845.  When an existing interrupt handler is completely replaced with a new,
  18846.  customized routine, the existing vector must be saved so it can be restored
  18847.  later. Although it is possible to modify the 4-byte vector by directly
  18848.  addressing the vector table in low RAM (and many published programs have
  18849.  followed this practice), any program that does so runs the risk of causing
  18850.  system failure when the program is used with multitasking or multiuser
  18851.  enhancements or with future versions of DOS. The only technique that can be
  18852.  recommended for either obtaining the existing vector values or changing them
  18853.  is to use the MS-DOS functions provided for this purpose: Interrupt 21H
  18854.  Functions 25H (Set Interrupt Vector) and 35H (Get Interrupt Vector).
  18855.  
  18856.  After the existing vector has been saved, it can be replaced with a far
  18857.  pointer to the replacement routine. The new routine must end with an IRET
  18858.  instruction. It should also take care to preserve all microprocessor
  18859.  registers and conditions at entry and restore them before returning.
  18860.  
  18861.  Replacement Handler
  18862.  
  18863.  Suppose a program performs many mathematical calculations of random values.
  18864.  To prevent abnormal termination of the program by the default MS-DOS
  18865.  Interrupt 00H handler when a DIV or IDIV instruction is attempted and the
  18866.  divisor is zero, a programmer might want to replace the Interrupt 00H
  18867.  (Divide by Zero) routine with one that informs the user of what has happened
  18868.  and then continues operation without abnormal termination. The COM program
  18869.  DIVZERO.ASM (see Figure 9) does just that.
  18870.  
  18871.  Supplementary Handlers
  18872.  
  18873.  In many cases, a custom interrupt handler augments, rather than replaces,
  18874.  the existing routine. The added routine might process some data before
  18875.  passing the data to the existing routine, or it might do the processing
  18876.  afterward. These cases require slightly different coding for the handler.
  18877.  
  18878.  If the added routine is to process data before the existing handler does,
  18879.  the routine need only jump to the original handler after completing its
  18880.  processing. This jump can be done indirectly, with the same pointer used to
  18881.  save the original content of the vector for restoration at exit. For
  18882.  example, a replacement Interrupt 08H handler that merely increments an
  18883.  internal flag at each timer tick can look something like the code in Figure
  18884.  10.
  18885.  
  18886.  The added handler must preserve all registers and machine conditions, except
  18887.  for those machine conditions that it will modify, such as the value of
  18888.  myflag in the example (and the flags register, which is saved by the
  18889.  interrupt action), and it must restore those registers and conditions before
  18890.  performing the jump to the original handler.
  18891.  
  18892.  A more complex situation arises when a replacement handler does some
  18893.  processing after the original routine executes, especially if the
  18894.  replacement handler is not reentrant. To allow for this processing, the
  18895.  replacement handler must prevent nested interrupts, so that even if the old
  18896.  handler (which is chained to the replacement handler by a CALL instruction)
  18897.  issues an EOI, the replacement handler will not be interrupted during
  18898.  postprocessing. For example, instead of using the preceding Interrupt 08H
  18899.  example routine, the programmer could use the code shown in Figure 11 to
  18900.  implement myflag as a semaphore and use the XCHG instruction to test it.
  18901.  
  18902.  Note that an interrupt handler of this type must simulate the original call
  18903.  to the interrupt routine by first doing a PUSHF, followed by a far CALL via
  18904.  the saved pointer to execute the original handler routine. The flags
  18905.  register pushed onto the stack is restored by the IRET of the original
  18906.  handler. Upon return from the original code, the new routine can preserve
  18907.  the machine state and do its own processing, finally returning to the caller
  18908.  by means of its own IRET.
  18909.  
  18910.  The flags inside the new routine need not be preserved, as they are
  18911.  automatically restored by the IRET instruction. Because of the nature of
  18912.  interrupt servicing, the service routine should not depend on any
  18913.  information in the flags register, nor can it return any information in the
  18914.  flags register. Note also that the previous handler (invoked by the indirect
  18915.  CALL) will almost certainly have dismissed the interrupt by sending an EOI
  18916.  to the 8259A PIC. Thus, the machine state is not the same as in the first
  18917.  myint8 example.
  18918.  
  18919.  To remove the new vector and restore the original, the program simply
  18920.  replaces the new vector (in the vector table) with the saved copy. If the
  18921.  substituted routine is part of an application program, the original vector
  18922.  must be restored for every possible method of exiting from the program
  18923.  (including Control-Break, Control-C, and critical-error Abort exits).
  18924.  Failure to observe this requirement invariably results in system failure.
  18925.  Even though the system failure might be delayed for some time after the exit
  18926.  from the offending program, as soon as some subsequent program overlays the
  18927.  interrupt handler code the crash is imminent.
  18928.  
  18929.  Summary
  18930.  
  18931.  Hardware interrupt handler routines, although not strictly a part of DOS,
  18932.  form an integral part of many MS-DOS programs and are tightly constrained by
  18933.  MS-DOS requirements. Routines of this type play important roles in the
  18934.  functioning of the IBM personal computers, and, with proper design and
  18935.  programming, significantly enhance product reliability and performance. In
  18936.  some instances, no other practical method exists for meeting performance
  18937.  requirements.
  18938.  
  18939.  Figure 1
  18940.  
  18941.  Interrupt
  18942.  
  18943.  Number    Definition
  18944.  
  18945.  00H    Divide by zero
  18946.  
  18947.  01H    Single step
  18948.  
  18949.  02H    Nonmaskable interrupt (NMI)
  18950.  
  18951.  03H    Breakpoint trap
  18952.  
  18953.  04H    Overflow trap
  18954.  
  18955.  05H    BOUND range exceeded (see note 1)
  18956.  
  18957.  06H    Invalid opcode (see note 1)
  18958.  
  18959.  07H    Coprocessor not available (see note 2)
  18960.  
  18961.  08H    Double-fault exception (see note 2)
  18962.  
  18963.  09H    Coprocessor segment overrun (see note 2)
  18964.  
  18965.  0AH    Invalid task state segment (see note 2)
  18966.  
  18967.  0BH    Segment not present (see note 2)
  18968.  
  18969.  0CH    Stack exception (see note 2)
  18970.  
  18971.  0DH    General protection exception (see note 2)
  18972.  
  18973.  0EH    Page fault (see note 3)
  18974.  
  18975.  0FH    (Reserved)
  18976.  
  18977.  10H    Coprocessor error (see note 2)
  18978.  
  18979.  Note 1:    The 80186, 80286, and 80386 microprocessors only.
  18980.  
  18981.  Note 2:    The 80286 and 80386 microprocessors only.
  18982.  
  18983.  Note 3:    The 80386 microprocessor only.
  18984.  
  18985.  Figure 2
  18986.  
  18987.      Interrupt
  18988.  
  18989.      Number        Definition
  18990.  
  18991.      05H        Print screen
  18992.  
  18993.      06H        Unused
  18994.  
  18995.      07H        Unused
  18996.  
  18997.      08H        Hardware IRQ0 (timer-tick) (see note 1)
  18998.  
  18999.      09H        Hardware IRQ1 (keyboard)
  19000.  
  19001.      0AH        Hardware IRQ2 (reserved) (see note 2)
  19002.  
  19003.      0BH        Hardware IRQ3 (COM2)
  19004.  
  19005.      0CH        Hardware IRQ4 (COM1)
  19006.  
  19007.      0DH        Hardware IRQ5 (fixed disk)
  19008.  
  19009.      0EH        Hardware IRQ6 (floppy disk)
  19010.  
  19011.      0FH        Hardware IRQ7 (printer)
  19012.  
  19013.      10H        Video service
  19014.  
  19015.      11H        Equipment information
  19016.  
  19017.      12H        Memory size
  19018.  
  19019.      13H        Disk I/O service
  19020.  
  19021.      14H        Serial-port service
  19022.  
  19023.      15H        Cassette/network service
  19024.  
  19025.      16H        Keyboard service
  19026.  
  19027.      17H        Printer service
  19028.  
  19029.      18H        ROM BASIC
  19030.  
  19031.      19H        Restart system
  19032.  
  19033.      1AH        Get/Set time/date
  19034.  
  19035.      1BH        Control-Break (user defined)
  19036.  
  19037.      1CH        Timer tick (user defined)
  19038.  
  19039.      1DH        Video parameter pointer
  19040.  
  19041.      1EH        Disk parameter pointer
  19042.  
  19043.      1FH        Graphics character table
  19044.  
  19045.      Note 1:    IRQ =  Interrupt request line.
  19046.  
  19047.      Note 2:    See figures 7 and 8.
  19048.  
  19049.  Figure 6
  19050.  
  19051.  IRQ
  19052.  
  19053.  Line    Interrupt    Description
  19054.  
  19055.  IRQ0    08H    Timer tick, 18.2 times per second
  19056.  
  19057.  IRQ1    09H    Keyboard service required
  19058.  
  19059.  IRQ2    0AH    I/O channel (unused on IBM PC/XT)
  19060.  
  19061.  IRQ3    0BH    COM1 service required
  19062.  
  19063.  IRQ4    0CH    COM2 service required
  19064.  
  19065.  IRQ5    0DH    Fixed-disk service required
  19066.  
  19067.  IRQ6    0EH    Floppy-disk service required
  19068.  
  19069.  IRQ7    0FH    Data request from parallel printer
  19070.  
  19071.          (see note 1)
  19072.  
  19073.  Note 1: This request cannot be reliably generated by older versions of the
  19074.  IBM Monochrome/Printer Adapter and compatibles. Printer drivers that depend
  19075.  on this signal for operation with these cards are subject to failure.
  19076.  
  19077.  Figure 7
  19078.  
  19079.  IRQ
  19080.  
  19081.  Line    Interrupt    Description
  19082.  
  19083.  IRQ0    08H    Timer tick, 18.2 times per second
  19084.  
  19085.  IRQ1    09H    Keyboard service required
  19086.  
  19087.  IRQ2    0AH    INT from slave 8259A:
  19088.  
  19089.  IRQ8    70H        Real-time clock service
  19090.  
  19091.  IRQ9    71H        Software redirected to IRQ2
  19092.  
  19093.  IRQ10    72H        Reserved
  19094.  
  19095.  IRQ11    73H        Reserved
  19096.  
  19097.  IRQ12    74H        Reserved
  19098.  
  19099.  IRQ13    75H        Numeric coprocessor
  19100.  
  19101.  IRQ14    76H        Fixed-disk controller
  19102.  
  19103.  IRQ15    77H        Reserved
  19104.  
  19105.  IRQ3    0BH    COM2 service required
  19106.  
  19107.  IRQ4    0CH    COM1 service required
  19108.  
  19109.  IRQ5    0DH    Data request from LPT2
  19110.  
  19111.  IRQ6    0EH    Floppy-disk service required
  19112.  
  19113.  IRQ7    0FH    Data request from LPT1
  19114.  
  19115.  Figure 9
  19116.  
  19117.  name    divzero
  19118.      title    'DIVZERO - Interrupt 00H Handler'
  19119.  ;
  19120.  ;DIVZERO.ASM: Demonstration Interrupt 00H Handler
  19121.  ;This code is specific to 80286 and 80386 microprocessors.
  19122.  ;To assemble, link, and convert to a COM file:
  19123.  ;
  19124.  ;    MASM DIVZERO
  19125.  ;    LINK DIVZERO
  19126.  ;    EXE2BIN DIVZERO.EXE DIVZERO.COM
  19127.  ;    DEL DIVZERO.EXE
  19128.  ;
  19129.  
  19130.  cr    equ    0dh    ; ASCII carriage return
  19131.  lf    equ    0ah    ; ASCII linefeed
  19132.  eos    equ    '$'    ; end of string marker
  19133.  
  19134.  _TEXT    segment    word public 'CODE'
  19135.  
  19136.      assume    cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT
  19137.  
  19138.      org    100h
  19139.  
  19140.  entry:    jmp    start    ; skip over data area
  19141.  
  19142.  intmsg    db    'Divide by Zero Occurred!',cr,lf,eos
  19143.  
  19144.  divmsg    db    'Dividing '    ; message used by demo
  19145.  par1    db    '0000h'    ; dividend goes here
  19146.      db    ' by '
  19147.  par2    db    '00h'    ; divisor goes here
  19148.      db    ' equals '
  19149.  par3    db    '00h'    ; quotient here
  19150.      db    'remainder'
  19151.  par4    db    '00h'    ; and remainder here
  19152.      db    cr,lf,eos
  19153.  
  19154.  oldint0    dd    ?    ; save old Int00H vector
  19155.  
  19156.  intflag    db    0    ; nonzero if divide by
  19157.              ; zero interrupt occurred
  19158.  
  19159.  oldip    dw    0    ; save old IP value
  19160.  
  19161.  
  19162.  ;
  19163.  ;The routine 'int0' is the actual divide by zero interrupt handler.
  19164.  ;It gains control whenever a divide by zero or overflow occurs. Its
  19165.  ;action is to set a flag and then increment the instruction pointer
  19166.  ;saved on the stack so that the failing divide will not be reexecuted
  19167.  ;after the IRET.
  19168.  ;
  19169.  ;In this particular case we can call MS-DOS to display a message during
  19170.  ;interrupt handling because the application triggers the interrupt
  19171.  ;intentionally. Thus, it is known that MS-DOS or other interrupt ;handlers
  19172.  are not in control at the point of interrupt.
  19173.  ;
  19174.  
  19175.  int0:    pop        cs:oldip        ;capture instruction pointer
  19176.  
  19177.      push    ax
  19178.      push    bx
  19179.      push    cx
  19180.      push    dx
  19181.      push    di
  19182.      push    si
  19183.      push    ds
  19184.      push    es
  19185.  
  19186.      push    cs    ; set DS = CS
  19187.      pop    ds
  19188.  
  19189.      mov    ah,09h    ; print error message
  19190.      mov    dx,offset _TEXT:intmsg
  19191.      int    21h
  19192.  
  19193.      add    oldip,2    ; bypass instruction causing
  19194.              ; divide by zero error
  19195.  
  19196.      mov    intflag,1    ; set divide by 0 flag
  19197.  
  19198.      pop    es    ; restore all registers
  19199.      pop    ds
  19200.      pop    si
  19201.      pop    di
  19202.      pop    dx
  19203.      pop    cx
  19204.      pop    bx
  19205.      pop    ax
  19206.  
  19207.      push    cs:oldip    ; restore instruction pointer
  19208.  
  19209.      iret        ; return from interrupt
  19210.  
  19211.  
  19212.  ;
  19213.  ;The code beginning at 'start' is the application program. It alters
  19214.  ;the vector for Interrupt 00H to point to the new handler, carries
  19215.  ;out some divide operations (including one that will trigger an
  19216.  ;interrupt) for demonstration purposes, restores the original
  19217.  ;contents of the Interrupt 00H vector, and then terminates.
  19218.  ;
  19219.  
  19220.  start:    mov    ax,3500h    ; get current contents
  19221.      int    21h    ; ofInt 00H vector
  19222.  
  19223.              ; save segment:offset
  19224.              ; of previous Int 00H handler
  19225.      mov    word ptr oldint0,bx
  19226.      mov    word ptr oldint0+2,es
  19227.  
  19228.              ; install new handler ...
  19229.      mov    dx,offset int0    ; DS:DX = handler address
  19230.      mov    ax,2500h    ; call MS-DOS to set
  19231.      int    21h    ; Int 00H vector
  19232.  
  19233.              ; now our handler is active,
  19234.              ; carry out some test divides.
  19235.  
  19236.      mov    ax,20h    ; test divide
  19237.      mov    bx,1    ; divide by 1
  19238.      call    divide
  19239.  
  19240.      mov    ax,1234h    ; test divide
  19241.      mov    bx,5eh    ; divide by 5EH
  19242.      call    divide
  19243.  
  19244.      mov    ax,5678h    ; test divide
  19245.      mov    bx,7fh    ; divide by 127
  19246.      call    divide
  19247.  
  19248.      mov    ax,20h    ; test divide
  19249.      mov    bx,0    ; divide by 0
  19250.      call    divide    ; (triggers interrupt)
  19251.  
  19252.              ; demonstration complete,
  19253.              ; restore old handler
  19254.  
  19255.      lds    dx,oldint0    ; DS:DX = handler address
  19256.      mov    ax,2500h    ; call MS-DOS to set
  19257.      int    21h    ; Int 00H vector
  19258.  
  19259.      mov    ax,4c00h    ; final exit to MS-DOS
  19260.      int    21h    ; with return code = 0
  19261.  
  19262.  
  19263.  ;
  19264.  ;The routine 'divide' carries out a trial division, displaying the
  19265.  ;arguments and the results. It is called with AX = dividend and
  19266.  ;BL = divisor.
  19267.  ;
  19268.  
  19269.  divide    proc    near
  19270.  
  19271.      push    ax    ; save arguments
  19272.      push    bx
  19273.  
  19274.      mov    di,offset par1    ; convert dividend to
  19275.      call    wtoa    ; ASCII for display
  19276.  
  19277.      mov    ax,bx    ; convert divisor to
  19278.      mov    di,offset par2    ; ASCII for display
  19279.      call    btoa
  19280.  
  19281.      pop    bx    ; restore arguments
  19282.      pop    ax
  19283.  
  19284.      div    bl    ; perform the division
  19285.      cmp    intflag,0    ; divide by zero detected?
  19286.      jne    nodiv    ; yes, skip display
  19287.  
  19288.      push    ax    ; no, convert quotient to
  19289.      mov    di,offset par3    ; ASCII for display
  19290.      call    btoa
  19291.  
  19292.      pop    ax    ; convert remainder to
  19293.      xchg    ah,al    ; ASCII for display
  19294.      mov    di,offset par4
  19295.      call    btoa
  19296.  
  19297.      mov    ah,09h    ; show arguments, results
  19298.      mov    dx,offset divmsg
  19299.      int    21h
  19300.  
  19301.  nodiv:    mov    intflag,0    ; clear divide by 0 flag
  19302.      ret        ; and return to caller
  19303.  
  19304.  divide    endp
  19305.  
  19306.  
  19307.  
  19308.  
  19309.  wtoa    proc    near    ; convert word to hex ASCII
  19310.              ; call with AX = binary value
  19311.              ;           DI = addr for string
  19312.              ; returns AX, CX, DI destroyed
  19313.  
  19314.      push    ax    ; save original value
  19315.      mov    al,ah
  19316.      call    btoa    ; convert upper byte
  19317.      add    di,2    ; increment output address
  19318.      pop    ax
  19319.      call    btoa    ; convert lower byte
  19320.      ret        ; return to caller
  19321.  
  19322.  wtoa    endp
  19323.  
  19324.  
  19325.  
  19326.  btoa    proc        near    ; convert byte to hex ASCII
  19327.              ; call with AL = binary value
  19328.              ;           DI = addr to store string
  19329.              ; returns AX, CX destroyed
  19330.  
  19331.      mov    ah,al    ; save lower nibble
  19332.      mov    cx,4    ; shift right 4 positions
  19333.      shr    al,cl    ; to get upper nibble
  19334.      call    ascii    ; convert 4 bits to ASCII
  19335.      mov    [di],al    ; store in output string
  19336.      mov    al,ah    ; get back lower nibble
  19337.  
  19338.      and    al,0fh    ; blank out upper one
  19339.      call    ascii    ; convert 4 bits to ASCII
  19340.      mov    [di+1],al    ; store in output string
  19341.      ret        ; back to caller
  19342.  
  19343.  btoa    endp
  19344.  
  19345.  
  19346.  
  19347.  ascii    proc    near    ; convert AL bits 0-3 to
  19348.              ; ASCII (0...9,A...F)
  19349.      add    al,'0'    ; and return digit in AL
  19350.      cmp    al,'9'
  19351.      jle    ascii2
  19352.      add    al,'A'-'9'-1    ; "fudge factor" for A-F
  19353.  ascii2    ret        ; return to caller
  19354.  
  19355.  ascii   endp
  19356.  
  19357.  _TEXT   ends
  19358.  
  19359.      end    entry
  19360.  
  19361.  
  19362.  
  19363.  Figure 10
  19364.  
  19365.  ■
  19366.      ■
  19367.      ■
  19368.  
  19369.  myflag    dw    ?    ; variable to be incremented
  19370.              ; on each timer-tick interrupt
  19371.  
  19372.  oldint8 dd    ?    ; contains address of previous
  19373.              ; timer-tick interrupt handler
  19374.      ■
  19375.      ■        ; get the previous contents
  19376.      ■        ; of the Interrupt 08H vector...
  19377.      mov    ax,3508h    ; AH = 35H (Get Interrupt Vector)
  19378.      int    21h    ; AL = Interrupt number (08H)
  19379.      mov    word ptr oldint8,bx    ; save the address of the
  19380.      mov    word ptr oldint8+2,es    ; previous Int 08H Handler
  19381.      mov    dx,seg myint8    ; put address of the new
  19382.      mov    ds,dx    ; interrupt handler into DS:DX
  19383.      mov    dx,offset myint8    ; and call MS-DOS to set vector
  19384.      mov    ax,2508h    ; AH = 25H (Set Interrupt; Vector)
  19385.      int    21h    ; AL = Interrupt number (08H)
  19386.      ■
  19387.      ■
  19388.      ■
  19389.  
  19390.  
  19391.  myint8:        ; this is the new handler
  19392.              ; for Interrupt 08H
  19393.  
  19394.      inc    cs:myflag    ; increment variable on each
  19395.              ; timer-tick interrupt
  19396.  
  19397.      jmp    dword ptr cs:[oldint8]    ; then chain to the
  19398.              ; previous interrupt handler
  19399.  
  19400.  
  19401.  
  19402.  Figure 11
  19403.  
  19404.  myint8:            ; this is the new handler
  19405.              ; for Interrupt 08H
  19406.  
  19407.      mov    ax,1    ; test and set interrupt-
  19408.      xchg    cs:myflag,ax    ; handling-in-progress
  19409.              ; semaphore
  19410.  
  19411.      push    ax    ; save the semaphore
  19412.  
  19413.      pushf        ; simulate interrupt,
  19414.              ; allowing the previous
  19415.      call    dword ptrcs:oldint8    ; handler for the
  19416.              ; Interrupt 08H
  19417.              ; vector to run
  19418.  
  19419.      pop    ax    ; get the semaphore back
  19420.      or    ax,ax    ; is our interrupt handler
  19421.              ; already running?
  19422.  
  19423.      jnz    myint8x    ; yes, skip this one
  19424.  
  19425.  
  19426.      ■        ; now perform our interrupt
  19427.      ■        ; processing here...
  19428.      ■
  19429.  
  19430.  
  19431.      mov    cs:myflag,0    ; clear the interrupt-
  19432.              ; handling-in-progress
  19433.              ; flag
  19434.  
  19435.  myint8x:
  19436.      iret        ; return from interrupt
  19437.  
  19438.  
  19439.  
  19440.  Advanced Techniques for Using Structures and Unions  In Your C Code
  19441.  
  19442.   Greg Comeau
  19443.  
  19444.  Structures, unions, and typedefs, constructs essential to organizing data in
  19445.  your programs, are often misunderstood and poorly used. In "Organizing Data
  19446.  in Your C Programs with Structures, Unions, and Typedefs," MSJ (Vol. 4, No.
  19447.  2), I attempted to clear up some of the mysteries surrounding the use of the
  19448.  constructs. In this article, I build on that discussion by examining
  19449.  pointers to structures and then move on to some of the finer points of
  19450.  multilevel structure access, memory allocation, and arrays.
  19451.  
  19452.  The previous article may have convinced some of you to use typedef more
  19453.  often in your programs. However, when doing so, you should be aware of one
  19454.  particular problem; structures that contain references to themselves in the
  19455.  form of a pointer can begin to produce mysterious syntax errors. Figure 1
  19456.  shows an example of such a situation. The problem here is that the typedef
  19457.  for the identifier s1 has not yet been completed on line 2; therefore the
  19458.  compiler cannot acknowledge the existence of the s1 typedef or even the
  19459.  existence of any name for s1 and must deduce that an invalid type is being
  19460.  used.
  19461.  
  19462.  The case shown in Figure 2 is a different situation but a similar problem.
  19463.  Two typedefs are set up; however, they each refer to the other (in circular
  19464.  fashion). In this instance, the first typedef always gives an error since
  19465.  the second one does not yet exist.
  19466.  
  19467.  Is there a solution to these situations? There are a few; however, some are
  19468.  wrong and some are messy. Without a clear syntactic way to remedy this, many
  19469.  programmers are tempted to change the s1 * and/or the s2 * into char * (yes,
  19470.  either one since the typedefs may actually be found in #include files whose
  19471.  order of occurrence in your source file is not determinable) and then, when
  19472.  using or assigning to s1ptr or s2ptr, will cast it into s1 * or s2 *,
  19473.  respectively. This is a mess and a sure way to get into trouble with a
  19474.  different compiler or hardware. Casting into s1 * or s2 * is a sure bet that
  19475.  something will go wrong sooner or later since the cast will keep the
  19476.  compiler from saying anything (that is, a cast is a directive to tell the
  19477.  compiler that you've decided that you know what you're doing with a
  19478.  mismatched type. Suffice it to say for now that even though a cast implies
  19479.  portability, it in no way guarantees it).
  19480.  
  19481.  The description of the problem having been stated, let's discuss possible
  19482.  ways to go about solving it. We'll begin by breaking the problem down into
  19483.  various steps. As we move along I will introduce some other concepts that
  19484.  you might not be aware of along the way. Let me just say in passing that
  19485.  Pascal fans will be glad to hear that the solution is to use a forward
  19486.  reference. We will go through several variations of this technique.
  19487.  
  19488.  Figure 1
  19489.  
  19490.  1   typedef struct {
  19491.  2       s1      *s1ptr;
  19492.  3       char    s1data[100];
  19493.  4   } s1;
  19494.  5
  19495.  6   main()
  19496.  7   {
  19497.  8       s1    s1instance;
  19498.  9
  19499.  10       /* ... */
  19500.  11   }
  19501.  
  19502.  Figure 2
  19503.  
  19504.  1  typedef struct {
  19505.   2      s2      *s2ptr;
  19506.   3      char    s1data[100];
  19507.   4  } s1;
  19508.   5
  19509.   6  typedef struct {
  19510.   7      s1      *s1ptr;
  19511.   8      char    s2data[100];
  19512.   9  } s2;
  19513.  10
  19514.  11  main()
  19515.  12  {
  19516.  13      s1    s1instance;
  19517.  14      s2    s2instance;
  19518.  15
  19519.  16      /* ... */
  19520.  17  }
  19521.  
  19522.  Forward References
  19523.  
  19524.  Let's make the problem simpler for a moment by removing the typedef from the
  19525.  examples (Figure 3). At this point it should be obvious that the ability to
  19526.  reference a pointer to s1 or s2 in the code sample does not exist. In other
  19527.  words, s1 and s2 are clearly not types. However, what if we were to add a
  19528.  structure tag to each declaration first so that the structure shape can be
  19529.  referenced instead (Figure 4)? The key here is that now s2ptr and s1ptr must
  19530.  not deal with a "type of;" they only serve as references to some
  19531.  structure_tag.
  19532.  
  19533.  Note, however, that though a forward reference through a pointer declaration
  19534.  is legitimate, an instance of the structure is not. If we look at Figure 5,
  19535.  the declaration of anotherptr is fine, since the pointer does not yet have
  19536.  to know what the shape of the structure it points to (the shape of the
  19537.  struct anothertag) looks like. In C, this is one way that an incomplete type
  19538.  can exist. Of course, if you wish to use anotherptr beyond having the
  19539.  pointer assigned to it, you must first define the structure to which it
  19540.  points.
  19541.  
  19542.  Similarly, the declaration of anotherinstance will fail, since I was
  19543.  attempting to include a whole instance of anothertag within sometag. This
  19544.  can't happen because the compiler has no way to determine sometag's members
  19545.  and therefore it is sizeless. The compiler can't delay this either since
  19546.  sometag would not have a valid size and shape until anothertag had a size
  19547.  and shape, thus creating an error.
  19548.  
  19549.  The problem of incomplete types in C is an important one to be aware of. It
  19550.  actually goes well beyond the discussion presented here and is not related
  19551.  to structures only. In general, you can always refer to an incomplete type
  19552.  but can never use it as a single entity, that is, as a unit by itself, since
  19553.  it is not completely defined. Another popular place where incomplete types
  19554.  are commonly used is with external arrays, for example, in a declaration
  19555.  such as extern int array[ ];. Here you can index and get the address of the
  19556.  array as normal; however, you cannot perform some operations such as
  19557.  sizeof(array) since the dimension of the array may not be in the same source
  19558.  file of such a declaration. This happens because array only serves as a
  19559.  declaration and not a definition.
  19560.  
  19561.  Figure 3
  19562.  
  19563.  1   struct {
  19564.   2       s2      *s2ptr;
  19565.   3       char    s1data[100];
  19566.   4   } s1;
  19567.   5
  19568.   6   struct {
  19569.   7       s1      *s1ptr;
  19570.   8       char    s2data[100];
  19571.   9   } s2;
  19572.  10
  19573.  11   main()
  19574.  12   {
  19575.  13       s1    s1instance;
  19576.  14       s2    s2instance;
  19577.  15
  19578.  16       /* ... */
  19579.  17   }
  19580.  
  19581.  Figure 4
  19582.  
  19583.  1    struct s1_tag {
  19584.   2        struct s2_tag  *s2ptr;
  19585.   3        char    s1data[100];
  19586.   4    };
  19587.   5
  19588.   6    struct s2_tag {
  19589.   7        struct s1_tag  *s1ptr;
  19590.   8        char    s2data[100];
  19591.   9    };
  19592.  10
  19593.  11    main()
  19594.  12    {
  19595.  13        struct s1_tag s1instance;
  19596.  14        struct s2_tag s2instance;
  19597.  15
  19598.  16        /* ... */
  19599.  17    }
  19600.  
  19601.  Figure 5
  19602.  
  19603.  1    struct atag {
  19604.   2        struct anothertag  *anotherptr;
  19605.   3        /* reference to another tag before
  19606.   4           it comes into scope is fine */
  19607.   5    };
  19608.   6
  19609.   7    struct sometag {
  19610.   8        struct anothertag  anotherinstance;
  19611.   9            /* This is an error since a
  19612.  10               description of anothertag
  19613.  11               is not in scope */
  19614.  12    };
  19615.  13
  19616.  14    main()
  19617.  15    {
  19618.  16        /* ... */
  19619.  17    }
  19620.  
  19621.  Typedef/Structure Tags
  19622.  
  19623.  Generally speaking, when you are considering the use of #define and typedef,
  19624.  it is usually safe to conclude that typedef is the better choice. Here I
  19625.  have introduced yet another major source of confusion when using structs by
  19626.  using structure tags. However, do not get the mistaken impression that I'm
  19627.  saying tags are better than typedefs, since this is not necessarily the
  19628.  case. I've only used tags as a stepping stone.
  19629.  
  19630.  For instance, now that forward references are clearer, let's turn back to
  19631.  the original problem and solve that using typedefs. There are two common
  19632.  ways to set this up (see Figures 6 and 7)--both are equivalent. In the
  19633.  first, two typedefs are set up that refer to structure tags (and since a
  19634.  typedef only serves as a synonym, we only care that it matches a structure
  19635.  tag that can be defined at a later time). This brings the two typedefs into
  19636.  scope and lets you use them from that point onward, simply using the names
  19637.  s1 and s2 as if they were types. This is true even when using them within
  19638.  the definitions of the structure tags that they were based upon, as shown in
  19639.  Figure 6.
  19640.  
  19641.  The second method (Figure 7) allows us to produce the same result using a
  19642.  different method. Actually it's the reverse of the previous procedure:
  19643.  instead of typedefing the names and creating the structure tags last, we'll
  19644.  create the structure tags as we're typedefing by using structure tags
  19645.  internally.
  19646.  
  19647.  Figure 6
  19648.  
  19649.  1    typedef struct s2_tag s2;
  19650.   2    typedef struct s1_tag s1;
  19651.   3
  19652.   4    struct s1_tag {
  19653.   5        s2  *s2ptr;
  19654.   6        char    s1data[100];
  19655.   7    };
  19656.   8
  19657.   9    struct s2_tag {
  19658.  10        s1  *s1ptr;
  19659.  11        char    s2data[100];
  19660.  12    };
  19661.  13
  19662.  14    main()
  19663.  15    {
  19664.  16        struct s1_tag s1instance;
  19665.  17        struct s2_tag s2instance;
  19666.  18
  19667.  19        /* ... */
  19668.  20    }
  19669.  
  19670.  Figure 7
  19671.  
  19672.  1    typedef struct s1_tag {
  19673.   2        struct s2_tag  *s2ptr;
  19674.   3        char    s1data[100];
  19675.   4    };
  19676.   5
  19677.   6    typedef struct s2_tag {
  19678.   7        struct s1_tag  *s1ptr;
  19679.   8        char    s2data[100];
  19680.   9    };
  19681.  10
  19682.  11    main()
  19683.  12    {
  19684.  13        struct s1_tag s1instance;
  19685.  14        struct s2_tag s2instance;
  19686.  15
  19687.  16        /* ... */
  19688.  17    }
  19689.  
  19690.  
  19691.  
  19692.  Passing/Returning Structs
  19693.  
  19694.  I'm assuming that the reader knows how to pass structures to functions. It
  19695.  should be sufficient to say that you should almost always pass a pointer to
  19696.  a structure or the address of a structure (&struct_var, which is mappable to
  19697.  a pointer to a structure) as the argument to a function instead of the
  19698.  actual structure itself. In other words, pass the structure by reference,
  19699.  not by value. The latter case can require an unreasonable amount of stack
  19700.  space and, if you are not careful, can cause symptoms that include random
  19701.  crashes, lockups, invalid pointers, and other such problems.
  19702.  
  19703.  Returning a structure should also be through a pointer, mostly for the sake
  19704.  of efficiency. This involves three areas: returning a complete structure
  19705.  from the function, returning a pointer to a structure, and the case where
  19706.  just some of the structure's members are accessed after function(s) return
  19707.  them.
  19708.  
  19709.  Returning a Structure
  19710.  
  19711.  When returning a complete structure from the function, consider what the
  19712.  compiler must go through. A typical compiler might copy the structure into a
  19713.  static area set aside by the compiler and linker. This area can be thought
  19714.  of as a union of every structure in your program. This of course means that
  19715.  it must be large enough to hold any given structure. It also needs to be
  19716.  addressable without any idiosyncrasies, and must be static with suitable
  19717.  alignment qualities.
  19718.  
  19719.  This copying alleviates much of the game playing the generated object code
  19720.  goes through since there is usually a standard function calling and return
  19721.  convention that the compiler writer will try to make the code adhere to.
  19722.  There are, however, problems. Consider the program in Figure 8, specifically
  19723.  the call to func on line 25. This should work as if f1 and f2 return base
  19724.  types, since you are, after all, allowed to return structures from
  19725.  functions. Note the series of events that must occur--f1 will return a
  19726.  struct ps1 by copying it into a static area; next that static area might be
  19727.  copied onto the stack, which is accomplished on some systems by generating
  19728.  lots of "move long word" instructions instead of a loop. The same will be
  19729.  performed for f2's return value.
  19730.  
  19731.  The end result is reached by a slow and tedious process. But that's only
  19732.  half the problem. If you pass the structures returned from f1 and f2 by
  19733.  their addresses as shown in line 26 (you can do this since a structure is an
  19734.  aggregate and not a simple scalar), you may find that your compiler does not
  19735.  work as expected. Both f1 and f2 might return the same address (remember our
  19736.  friend provided by the linker), thus the addresses and strings printed out
  19737.  by func2 could be the same.
  19738.  
  19739.  Figure 8
  19740.  
  19741.  1    struct ps1 {
  19742.   2        char    *p;
  19743.   3        char    array[1024 - sizeof(char *)];
  19744.   4    } v1;
  19745.   5
  19746.   6    struct ps2 {
  19747.   7        char    *p;
  19748.   8        char    array[512 - sizeof(char *)];
  19749.   9    } v2;
  19750.  10
  19751.  11    struct ps1 f1()
  19752.  12    {
  19753.  13        v1.p = "hi there";
  19754.  14        return (v1);
  19755.  15    }
  19756.  16
  19757.  17    struct ps2 f2()
  19758.  18    {
  19759.  19        v2.p = "HI THERE";
  19760.  20        return (v2);
  19761.  21    }
  19762.  22
  19763.  23    main()
  19764.  24    {
  19765.  25        func(f1(), f2());
  19766.  26        func2(&f1(), &f2()); /* This doesn't mean the
  19767.  27                             address of f1 and f2!
  19768.  28                             It means the address
  19769.  29                             of the structures they
  19770.  30                             return--Remember
  19771.  31                             precedence! */
  19772.  32    }
  19773.  33
  19774.  34    func(struct ps1 v1, struct ps2 v2)
  19775.  35
  19776.  36
  19777.  37    {
  19778.  38        printf("%s\n", v1.p);
  19779.  39        printf("%s\n", v2.p);
  19780.  40    }
  19781.  41    func2(struct ps1 *v1, struct ps2 *v2)
  19782.  42
  19783.  43
  19784.  44    {
  19785.  45        printf("%ld\n", v1); /* print out the addrs
  19786.  46                                that v1 and v2 point
  19787.  47                                to, these */
  19788.  48        printf("%ld\n", v2); /* may also be printed
  19789.  49                                with a %p instead of
  19790.  50                                %ld */
  19791.  51        printf("%s\n", v1->p);
  19792.  52        printf("%s\n", v2->p);
  19793.  53    }
  19794.  
  19795.  Returning a Pointer
  19796.  
  19797.  The second case is quite simple: don't return a pointer to a structure
  19798.  that's automatic. Figure 9 shows an example of such misuse. Notice that func
  19799.  returns &temp, which is perfectly valid C but not valid logic. When func
  19800.  exits, the temp structure--not the temp tag, which has file scope in
  19801.  this example--will terminate since it is local to a function and
  19802.  nonstatic. Since temp has terminated, accessing it will result in undefined
  19803.  behavior and most likely a program crash.
  19804.  
  19805.  Note that changing the declaration to static struct temp temp; will not
  19806.  cause any problems. Because temp is now static it will have a permanent
  19807.  existence during the life of your program. In this case it doesn't matter if
  19808.  the function that houses temp is currently executing or not; the fact is
  19809.  that temp is addressable since it's static.
  19810.  
  19811.  Additionally, whether temp is in scope or not is irrelevant here. Scope
  19812.  relates only to identifiers. It does not deal with concepts such as
  19813.  variables--for instance setting a pointer to an absolute position and
  19814.  accessing it--or things like addresses. Of course you will only be able
  19815.  to obtain an identifier's address while it is in scope; but once you have
  19816.  the address of an object, during its lifetime it's yours to do with as you
  19817.  please (within reason of course).
  19818.  
  19819.  Figure 9
  19820.  
  19821.  1  struct temp {
  19822.   2      int    members;
  19823.   3      /* ... */
  19824.   4  };
  19825.   5
  19826.   6  struct temp *
  19827.   7  func()
  19828.   8  {
  19829.   9      struct temp temp; /* yes, structure tags *and*
  19830.  10                           structures can have the
  19831.  11                           same name */
  19832.  12
  19833.  13      return (&temp);
  19834.  14  }
  19835.  15
  19836.  16  main()
  19837.  17  {
  19838.  18      struct temp *temp;
  19839.  19
  19840.  20      temp = func();
  19841.  21      /* <expressions using temp->???> */
  19842.  22  }
  19843.  23
  19844.  
  19845.  
  19846.  
  19847.  Member Access
  19848.  
  19849.  The last scenario is one in which a function returns a structure, but you're
  19850.  only interested in accessing a few of its members. Think about this for a
  19851.  moment. Should you access the members by returning the structure and be hit
  19852.  with all the copying or should you take the easy route by returning a
  19853.  pointer to the structure?
  19854.  
  19855.  In the case of an operating system call, you'll rarely want a pointer to one
  19856.  of the internal data structures of the operating system unless you're
  19857.  writing a device driver or a very specialized application that requires some
  19858.  special knowledge.
  19859.  
  19860.  In another situation you might find you're calling a C library function
  19861.  instead of a system call, and are most likely accessing something in your
  19862.  "process's space." But the arguments to the particular library you are using
  19863.  are usually a given and neither can nor should be changed. Routines may
  19864.  accept an argument that's a pointer to a structure (one that you've properly
  19865.  allocated based on some #include file) and completely avoid a return value,
  19866.  which would have been a structure.
  19867.  
  19868.  The routine itself will set the structure's members as might be appropriate.
  19869.  This will prevent the situation I've presented and is a viable way to code
  19870.  some of your programs. In addition, you might find routines of your own that
  19871.  use large structures, and instead of passing whole entities back and forth,
  19872.  it is often worth creating another smaller structure which is a subset of
  19873.  the larger one and manipulating that instead.
  19874.  
  19875.  Depending upon your logic and program design, you will also find it
  19876.  worthwhile to return a pointer to a structure even if only one of two
  19877.  members is going to be used. You only have to apply the arrow operator (->)
  19878.  to the returned pointer to access the member in question, rather than
  19879.  dealing with the whole structure. Remember that you may be calling a
  19880.  function containing an instance of the structure it's returning a pointer to
  19881.  as an internal static identifier. However, the consequence is that calling
  19882.  the function twice could destroy the previous value of the structure, so the
  19883.  calling function must account for this and copy all the data it needs before
  19884.  calling the previously called function again.
  19885.  
  19886.  This presents another situation in which either the caller of the function
  19887.  or the function itself dynamically allocated the structure. You must
  19888.  therefore be careful to control the allocation of the structure and be just
  19889.  as careful to make sure that you free it properly.
  19890.  
  19891.  Structure Access
  19892.  
  19893.  Figure 10 contains both structures with pointers to other structures and
  19894.  instances of the other structures. Chances are that you will never encounter
  19895.  this specific situation. Nevertheless I've included it to provide some
  19896.  further insights into structures in general.
  19897.  
  19898.  Most of you can no doubt construct a simple structure member reference as in
  19899.  line 21 of Figure 10. Some of you can even create a multistructure or
  19900.  multilevel reference as in line 23--but without certainty that it is
  19901.  correct (it is). Anything beyond this, however (meaning those nasty
  19902.  creatures we call pointers), is unfamiliar territory.
  19903.  
  19904.  To understand line 23, you must be aware that s3 contains an instance (that
  19905.  is, an occurrence) of an s2tag structure (an s2tag structure named s2inst),
  19906.  which in turn contains an instance to an s1tag structure (s1inst), which of
  19907.  course contains an instance of an integer identifier named s1var.
  19908.  
  19909.  To use each of these instances, you simply create a structure access to the
  19910.  member, as shown in line 21. Since the operator precedence of the dot (.)
  19911.  operator presents no problem and its associativity is naturally oriented to
  19912.  be left to right, the setup is simply
  19913.  
  19914.  structure1.<...>.structureN
  19915.  
  19916.  which line 23 shows.
  19917.  
  19918.  Remember that this all takes place within s3 because s3 contains an s2tag
  19919.  and s2tag contains an s1tag. Under a different case, such as in lines
  19920.  25-28, the code will use references to s3tag, s2tag, and s1tag by way
  19921.  of ps3, ps2, and ps1, which go beyond the limits of s3 to gain access to
  19922.  other variables such as s2 and s1.
  19923.  
  19924.  On line 25, ps3 is set equal to the address of s3. Note the use of &s3
  19925.  instead of s3 since we want to obtain the address of s3 and not an
  19926.  assignment to the pointer of the actual contents of the s3 structure. Once
  19927.  this is done, we can reference the members of the structure (s3 in this
  19928.  case) that ps3 points to by using the -> operator. It happens that we want
  19929.  to assign to ps2, which is another structure pointer (a pointer to an
  19930.  s2tag). This assignment will take place just as smoothly as the ps3
  19931.  assignment.
  19932.  
  19933.  The evaluation of the arrow operator will take place in exactly the same
  19934.  order of precedence as the dot operator. Again, this will occur with left to
  19935.  right associativity. Our knowledge of line 23 coupled with this discussion
  19936.  should make the interpretation of lines 27 and 28 very easy. Briefly then,
  19937.  line 27 uses the fact that ps3->ps2 references an s2tag, which contains a
  19938.  reference to an s1tag. This allows ps3->ps2->ps1 to be assigned to an
  19939.  identifier such as s1, which has an s1tag size and shape. Line 28 uses
  19940.  ps3->ps2->ps1, which is a pointer to an s1tag and therefore a reference to a
  19941.  member such as s1var is possible as well.
  19942.  
  19943.  Every pointer used in lines 25-27 needed to be initialized. You could
  19944.  not simply have coded the access to s1var in line 28 without the other
  19945.  assignments. That would not be valid logic since every pointer in this
  19946.  example must access a memory location in order to be used.
  19947.  
  19948.  Be careful here since this constraint is something that you as the
  19949.  programmer must take care to enforce. The compiler doesn't care, for
  19950.  instance, that you may have coded line 28 without lines 25 through 27. You
  19951.  may actually have performed the structure pointer assignments in those lines
  19952.  in another part of the program based on if/else logic and not necessarily
  19953.  right before line 28. In general the compiler has no easy way of determining
  19954.  this.
  19955.  
  19956.  The moral is to make sure all your assignments and pointers are set up
  19957.  properly since the compiler is not going to give you a warning or error
  19958.  message for failing to initialize them correctly. Problems of this nature
  19959.  will typically begin to crop up during the execution of your program; more
  19960.  often than not they will be sporadic and very hard to debug. If you provide
  19961.  the extra ounce of prevention during coding, you will avoid many of these
  19962.  situations.
  19963.  
  19964.  Line 29 really means less than you might think it does. If you take a closer
  19965.  look at the code, you will see that the execution of line 27 allowed line 28
  19966.  to gain access to s1.s1var. However, this may work properly even if ps3
  19967.  and/or ps3->ps2 are not valid pointers. Or I should say it appears to work
  19968.  properly--can you see the problem here?
  19969.  
  19970.  If we work under the assumption that line 25 was accidentally deleted from
  19971.  the source file, would the program continue to work properly? Perhaps. If it
  19972.  did continue to work, should it have? No. As explained above, it would most
  19973.  certainly compile so that's not the concern here. It would most likely
  19974.  execute under DOS as though nothing were wrong. However, most versions of
  19975.  the XENIX(R) operating system, as well as OS/2 systems, would produce a
  19976.  general protection fault because of the invalid memory access.
  19977.  
  19978.  This would occur because ps3 is an external (that is, an external defined in
  19979.  the same source file with no initializer) variable and would therefore be
  19980.  implicitly initialized to zero. If this is the case, then line 26 would be
  19981.  indexing ps2 off a pointer that points at memory location zero. That
  19982.  assignment would then be assigning something to a memory location of 0 +
  19983.  sizeof(int), which may map into memory location 2 and then be treated as the
  19984.  location of ps3->ps2, which we all know is wrong. However, if an access to
  19985.  that location at that moment in time does not stomp on something it
  19986.  shouldn't (and in many cases this is not as clear as the scenario that I'm
  19987.  describing), execution will continue with no apparent damage.
  19988.  
  19989.  Microsoft(R) C Optimizing Compiler versions 5.0 and later usually protect
  19990.  against the case above with their infamous R6001 run-time error message upon
  19991.  program termination; however, this is not something you should particularly
  19992.  depend upon, and it shouldn't be relied on to help solve your problems.
  19993.  Furthermore, the R6001 error functions only when your program writes in low
  19994.  core. Anything beyond that certain magic number and you're on your own.
  19995.  Therefore, make sure that your pointers are always legitimate regardless of
  19996.  whether or not your program executes properly.
  19997.  
  19998.  Clearly looks can be deceiving in this case. An excellent  example of this
  19999.  scenario is when you merge together different stubs to your program and
  20000.  suddenly something seems to be going wild. Most likely this is due to
  20001.  invalid pointers pointing to locations that cannot be touched without
  20002.  causing a problem. This would occur because your program modules' variables
  20003.  and functions will most likely be in different memory locations as well as
  20004.  in a larger program. When this happens, errors that didn't show up in the
  20005.  stubs will begin to appear.
  20006.  
  20007.  Given the preceding information, we're now ready to tackle some of the other
  20008.  derivations in Figure 10. For instance, if we wanted to access s1inst
  20009.  indirectly through the ps2 pointer, we might use code similar to line 31. If
  20010.  we read this left to right (since both -> and . have equal precedence and
  20011.  left to right associativity), you'll notice that the part referring to
  20012.  ps2.s1inst is in error. Since ps2 is a pointer, constructing anything with
  20013.  ps2. (note the dot) is not going to work because the dot operator requires
  20014.  that the left operand have a structure type. This structure type must be
  20015.  either an identifier reflecting a structure name, or a dereference of a
  20016.  parenthesized pointer to structure type as in ps=s;  (*ps).m;.
  20017.  
  20018.  The proper way to do this is shown on line 32, which is the same as line 27,
  20019.  only instead of referencing another pointer (ps1), we're accessing a real
  20020.  structure (s1inst).
  20021.  
  20022.  You may see another solution to this problem since you should know from your
  20023.  knowledge of C that a structure reference such as pointer->member is
  20024.  translatable into (*pointer).member. However, how do we implement it in this
  20025.  case? Line 33 seems like a good possibility to resolve this problem (let's
  20026.  not even consider the syntax of line 34) but it does not go far enough.
  20027.  Unfortunately, compiling this will not clue you in any better since most
  20028.  compilers will simply report a syntax error. This isn't immediately
  20029.  intuitive--at least not until you see exactly what's going on behind
  20030.  the scenes.
  20031.  
  20032.  You need to ask yourself: What exactly is ps2? We know it's a pointer, and
  20033.  we think we know its name but in fact its name is not ps2. If it were, you
  20034.  would be able to say something like ps2 = 0; and that is clearly
  20035.  unreasonable (compile it to prove it), since there is no identifier with a
  20036.  name consisting solely of ps2. Note that there is one named s3.ps2 and
  20037.  another that can reference it through pointer notation, as in ps3->ps2.
  20038.  Therefore, these are two names we must refer to in this particular program
  20039.  when accessing ps2.
  20040.  
  20041.  Tying this back to the (*pointer).member notation discussion above, the
  20042.  correct syntax for line 33 is shown in line 37 since ps3->ps2 is the proper
  20043.  pointer reference to ps2. This in no way infers line 38 is equivalent to
  20044.  line 37. As discussed in the previous article, this line would produce an
  20045.  error because of the precedence of the * operator.
  20046.  
  20047.  As a final note, make sure that you understand that lines 32 and 37 do not
  20048.  access the same s1var as in line 38. Again, recall that an s3tag contains a
  20049.  pointer to an s2tag and an instance of an s2tag--they are not the same
  20050.  thing. You could point the s2tag pointer (ps2) to the s2tag instance
  20051.  (s2inst), but if you had wanted to do that, you'd need to code lines
  20052.  39-42.
  20053.  
  20054.  Figure 10
  20055.  
  20056.  1   struct s1tag {
  20057.   2       int    s1var;
  20058.   3   } s1;
  20059.   4
  20060.   5   struct s2tag {
  20061.   6       int    s2var;
  20062.   7       struct s1tag *ps1;
  20063.   8       struct s1tag s1inst;
  20064.   9   } s2;
  20065.  10
  20066.  11   struct s3tag {
  20067.  12       int    s3var;
  20068.  13       struct s2tag *ps2;
  20069.  14       struct s2tag s2inst;
  20070.  15   } s3;
  20071.  16
  20072.  17   struct s3tag *ps3;
  20073.  18
  20074.  19   main()
  20075.  20   {
  20076.  21       s1.s1var = 99;
  20077.  22
  20078.  23       s3.s2inst.s1inst.s1var = 5;
  20079.  24
  20080.  25       ps3 = &s3;
  20081.  26       ps3->ps2 = &s2;
  20082.  27       ps3->ps2->ps1 = &s1;
  20083.  28       ps3->ps2->ps1->s1var = -99;
  20084.  29       printf("s1var=%d\n", s1.s1var);
  20085.  30
  20086.  31       /* ps3->ps2.s1inst.s1var = 11; */
  20087.  32       ps3->ps2->s1inst.s1var = 22;
  20088.  33       /* ps3->(*ps2).s1inst.s1var = 33; */
  20089.  34       /* ps3->*ps2.s1inst.s1var = 44; */
  20090.  35       /* ps2 = 0; */
  20091.  36         /* might as well have called this 'abccba' */
  20092.  37       (*ps3->ps2).s1inst.s1var = 55;
  20093.  38       /* *ps3->ps2.s1inst.s1var = 66; */
  20094.  39       printf("s3.s2inst.s1inst.s1var=%d\n",
  20095.  40                 s3.s2inst.s1inst.s1var);
  20096.  41
  20097.  42       ps3->ps2 = &ps3->s2inst;
  20098.  43       ps3->ps2->s1inst.s1var = 22;
  20099.  44       printf("s3.s2inst.s1inst.s1var=%d\n",
  20100.                     s3.s2inst.s1inst.s1var);
  20101.  45   }
  20102.  
  20103.  Multilevel Structure Access Involving Functions
  20104.  
  20105.  There are many situations in which you may call functions that return
  20106.  structures, or pointers to structures and the use of temporary variables
  20107.  becomes a nuisance. Consider the function example1 in Figure 11. Is it clear
  20108.  that all of the lines from 27 through 30 will print out 12345?
  20109.  
  20110.  Line 27 ought to be self-explanatory as a reference to s1.s1var, which has
  20111.  been statically initialized to the value 12345. Line 28 involves a structure
  20112.  analysis exactly like any other, with the same precedence and associativity
  20113.  of operators involved. Do you care that there is a ( ) involved? No, because
  20114.  you memorized the top line of the Operator Precedence/Associativity chart.
  20115.  Therefore since s1returner returns an s1tag type and explicitly returns s1,
  20116.  why not just treat it like line 27? It's simply a structure.member
  20117.  reference.
  20118.  
  20119.  Line 29 presents a function that returns a pointer to an s1tag, but nothing
  20120.  is that different here even though it does involve a function. If it returns
  20121.  a pointer, access it as a pointer->member reference. Of course line 30 is
  20122.  just a familiar pointer->member case being mapped into (*pointer).member.
  20123.  
  20124.  These variable references are all rather natural. The alternative is to code
  20125.  something along the lines of the statements shown in the example2 function.
  20126.  As you can see, this makes things longer and more tedious than necessary and
  20127.  in this program needn't be used. This point hits home when you realize that
  20128.  you may have a second structure involved, such as s2, and as in the previous
  20129.  section, there are pointers and references to all sorts of variables. This
  20130.  would result in code such as that found in example3 and example4 (or even
  20131.  more complex situations).
  20132.  
  20133.  Figure 11
  20134.  
  20135.  1   struct s1tag {
  20136.   2       int    s1var;
  20137.   3   } s1 = { 12345 };
  20138.   4
  20139.   5   struct s2tag {
  20140.   6       int    s2var;
  20141.   7       struct s1tag *ps1;
  20142.   8       struct s1tag s1inst;
  20143.   9   } s2;
  20144.  10
  20145.  11   struct s2tag *ps2;
  20146.  12
  20147.  13   struct s1tag
  20148.  14   s1returner()
  20149.  15   {
  20150.  16       return (s1);
  20151.  17   }
  20152.  18
  20153.  19   struct s1tag *
  20154.  20   ps1returner()
  20155.  21   {
  20156.  22       return (&s1);
  20157.  23   }
  20158.  24
  20159.  25   void example1()
  20160.  26   {
  20161.  27       printf("%d\n", s1.s1var);
  20162.  28       printf("%d\n", s1returner().s1var);
  20163.  29       printf("%d\n", ps1returner()->s1var);
  20164.  30       printf("%d\n", (*ps1returner()).s1var);
  20165.  31       printf("%d\n", (ps1returner())->s1var);
  20166.  32       /* printf("%d\n", (ps1returner()).s1var); */
  20167.  33   }
  20168.  34
  20169.  35   void example2()
  20170.  36   {
  20171.  37       struct s1tag s1holder;
  20172.  38       struct s1tag *ps1holder;
  20173.  39
  20174.  40       printf("%d\n", s1.s1var);
  20175.  41       s1holder = s1returner();
  20176.  42       printf("%d\n", s1holder.s1var);
  20177.  43       ps1holder = ps1returner();
  20178.  44       printf("%d\n", ps1holder->s1var);
  20179.  45       printf("%d\n", (*ps1holder).s1var);
  20180.  46   }
  20181.  47
  20182.  48   struct s2tag
  20183.  49   s2returner()
  20184.  50   {
  20185.  51       return (s2);
  20186.  52   }
  20187.  53
  20188.  54   struct s2tag *
  20189.  55   ps2returner()
  20190.  56   {
  20191.  57       return (&s2);
  20192.  58   }
  20193.  59
  20194.  60   void example3()
  20195.  61   {
  20196.  62       printf("%d\n", s2.s1inst.s1var);
  20197.  63       printf("%d\n", s2returner().s1inst.s1var);
  20198.  64       printf("%d\n", ps2returner()->s1inst.s1var);
  20199.  65   }
  20200.  66
  20201.  67   void example4()
  20202.  68   {
  20203.  69       printf("%d\n", ps2returner()->ps1->s1var);
  20204.  70   }
  20205.  71
  20206.  72   main()
  20207.  73   {
  20208.  74       example1();
  20209.  75       example2();
  20210.  76
  20211.  77       s2.s1inst.s1var = 54321;
  20212.  78       example3();
  20213.  79
  20214.  80       s2.ps1 = &s1;
  20215.  81       example4();
  20216.  82   }
  20217.  
  20218.  Structures and Malloc
  20219.  
  20220.  There is one other important point to consider. Typically, one must deal
  20221.  with packets of information. In other words, you may find that you're being
  20222.  fed some group of data that is prefixed with control or status information
  20223.  and is then followed by the actual data being described. This would normally
  20224.  occur when dealing with communications, but it needn't occur only there. The
  20225.  problem this presents to the C programmer is that the information part of
  20226.  the packet is finite and predictable, whereas the data portion may be
  20227.  variable in length. For instance, you may find that the following structure
  20228.  is sufficient for describing the control information:
  20229.  
  20230.  struct apacket {
  20231.     int   packethead;
  20232.     int   packetcontrol1;
  20233.     int   packetcontrol2;
  20234.     char  data[???];
  20235.  };
  20236.  
  20237.  But how large do you make the dimension of data[ ]?
  20238.  
  20239.  If you make it too small, you could lose data. If you make it too large, you
  20240.  could waste critical space in your program or machine state. Make it
  20241.  reasonably large, and you may not know if that size will be too small for
  20242.  the future--then what do you do? You could add another field that
  20243.  encodes the size of the data length, then derives some unions and associated
  20244.  code to perform something like the codedrecord example in the previous
  20245.  article. I'm sure we can all agree, however, that that would be a big mess.
  20246.  
  20247.  The best answer is to avoid the constraints altogether and work with what
  20248.  you have been given. In other words, since you know what the members of the
  20249.  structure that represent the control information are, why not use that to
  20250.  your advantage? This, along with the ability to allocate memory dynamically
  20251.  through standard library routines, such as alloc, calloc, and malloc, is all
  20252.  you need.
  20253.  
  20254.  A first attempt at a solution might result in a structure template and
  20255.  sample code such as:
  20256.  
  20257.  struct apacket {
  20258.     int    packethead;
  20259.     int    packetcontrol1;
  20260.     int    packetcontrol2;
  20261.     int    packetsize;
  20262.     char   data[0];
  20263.  };
  20264.      < other code >
  20265.  struct apacket apacket;
  20266.  struct apacket *ppacket;
  20267.  ppacket = (struct
  20268.     apacket *)malloc(
  20269.     sizeof(struct apacket) +
  20270.     apacket.packetsize);
  20271.  
  20272.  Unfortunately, C does not allow for zero-length data items to occur, even in
  20273.  structure tags. (Note that some compilers allow this, but they are clearly
  20274.  in error. This is not a portable construct and should be completely
  20275.  avoided.)
  20276.  
  20277.  Many of you will be quick to point out that making data into a 1-dimensional
  20278.  array with a bound of 1 (for example, char data[1]) should work without a
  20279.  problem (which is true). Note that the 1-byte length will need to be
  20280.  subtracted from the value that's being passed to malloc. This length can be
  20281.  presented as sizeof(char), sizeof(char [1]), or simply the constant 1 since
  20282.  in this case they all represent and refer to the same thing. By the way,
  20283.  because sizeof can accept a derived type as its argument, the second sizeof
  20284.  says, "give me the size of an array of x characters, where x is 1."
  20285.  
  20286.  An equal yet slightly less messy approach is to make use of the offsetof
  20287.  macro. Under this circumstance, the structure tag would still contain a char
  20288.  data[1], however instead of
  20289.  
  20290.  ppacket = (struct
  20291.          apacket *)malloc(
  20292.  sizeof(struct apacket) -
  20293.     sizeof(char[1]) +
  20294.     apacket.packetsize);
  20295.  
  20296.  you would code:
  20297.  
  20298.  ppacket = (struct
  20299.          apacket *)malloc(
  20300.  offsetof(apacket, data) +
  20301.     apacket.packetsize);
  20302.  
  20303.  since the offset of data within apacket would represent the length of the
  20304.  previous fields (in our case all the fields) in apacket. I feel this is a
  20305.  superior method. With offsetof it's perfectly clear: get the size of the
  20306.  structure and add it to the size desired for the data.
  20307.  
  20308.  Once you have a pointer of the correct length, you may copy the structure
  20309.  members as appropriate. However, before leaving this subject, let me once
  20310.  again refer you to the March article and encourage you to reread the
  20311.  sections dealing with structure holes and structure packing since you may
  20312.  find that you will need to set up your structures in the same "manner" as
  20313.  the one(s) which you are copying it from.
  20314.  
  20315.  This scheme of creating dynamic structure images is, as most things, not
  20316.  without problems and requires some careful thought before you use it. For
  20317.  instance, it should be clear that if you use this scheme, every reference to
  20318.  the packet's members will be through a pointer.
  20319.  
  20320.  This is due to the variable length data that must remain as a single unit.
  20321.  It's important to note that if you do not need a variable length structure,
  20322.  you should think about organizing your data in a less elaborate way. For
  20323.  example, you could change the apacket structure tag layout so that data is
  20324.  not an array but a character pointer.
  20325.  
  20326.  Allowing for this circumstance will usually be far more natural, since
  20327.  apacket instances must be set up (using a char *data; construct) and then
  20328.  referenced. Note that now all the structure members can be accessed directly
  20329.  with the dot operator. The close relationship between arrays and pointers
  20330.  allows data to be referenced in array notation, if desired, as follows:
  20331.  
  20332.  struct apacket somepacket;
  20333.  somepacket.data =
  20334.      (char *) malloc(
  20335.  somepacket.packetsize);
  20336.      <...>
  20337.  somepacket.data[i] =  <...>;
  20338.      <...>
  20339.  *somepacket.data = <...>;
  20340.  /* Note precedence with
  20341.     no parens! */
  20342.      <...>
  20343.  
  20344.  The only ramification of this example is that you will need to free the
  20345.  memory block associated with somepacket.data when you no longer need the
  20346.  data (for dynamically allocated data, the programmer controls the lifetime
  20347.  of the memory block).
  20348.  
  20349.  Arrays
  20350.  
  20351.  Since we are on the subject of dynamic memory, it's worth dwelling a bit on
  20352.  arrays and structures. First, look at line 21 of Figure 12. By now I would
  20353.  hope that you wouldn't have much of a problem interpreting it. Nor should
  20354.  you have a problem deciphering lines 24-26, where a pointer to a
  20355.  structure is set equal to an element of an array (which is therefore using
  20356.  only one structure).
  20357.  
  20358.  The use of arrays (and pointers) implies that their usage and idiosyncrasies
  20359.  remain the same regardless of whether they are used inside structures or as
  20360.  structures (for instance, the syntax and semantics of passing an array to a
  20361.  function does not change because the array might consist of structures
  20362.  rather than a base type like int).
  20363.  
  20364.  I'd also like to direct your attention to the use of HBOUND that occurs on
  20365.  line 20. It's actually very simple (as lines 17-18 demonstrate) and is
  20366.  very handy when dealing with a good many array situations.
  20367.  
  20368.  Finally, if you needed to use and access s1 strictly via pointers, an
  20369.  alternate to lines 20-22 can be found in lines 29-35. The latter
  20370.  lines are surely more cryptic; however, they do remove the indexing
  20371.  notation, which would be advantageous if you were to reference other
  20372.  elements of the structure or even repeated occurrences of the same element.
  20373.  
  20374.  Returning to HBOUND, you should study the constructs on lines 29 and
  20375.  32-33. The code is checking to see if the pointer has gone beyond the
  20376.  end of the array by checking it against an array dimension that is one
  20377.  greater than the array. In other words, ps1 will be checked against &s1[10],
  20378.  which is one greater than nine (remember that in C arrays start at element
  20379.  0). Similarly, cp will be checked against &ps1->charray[20]. Note in
  20380.  particular how all of this occurs without having to explicitly use any
  20381.  constants in either of the two nested loops that occur in this program. You
  20382.  should strive for this type of construction in your own programs, whether
  20383.  you are dealing with structures or not.
  20384.  
  20385.  Figure 12
  20386.  
  20387.  1   #define HBOUND(array)  (sizeof(array) /
  20388.   2     sizeof(array[0]))
  20389.   3
  20390.   4   struct s1tag {
  20391.   5       char    charray[20];
  20392.   6   };
  20393.   7
  20394.   8   struct s1tag s1[10];
  20395.   9   struct s1tag *ps1;
  20396.  10
  20397.  11   main()
  20398.  12   {
  20399.  13       int     i;
  20400.  14       int     j;
  20401.  15       char    *cp;
  20402.  16
  20403.  17       printf("%d/%d=%d\n", sizeof(s1),
  20404.  18                  sizeof(s1[0]), HBOUND(s1));
  20405.  19
  20406.  20       for (i = 0; i < HBOUND(s1); i++)
  20407.  21      for (j = 0; j < sizeof(s1[0].charray); j++)
  20408.  22          s1[i].charray[j] = '\0';
  20409.  23
  20410.  24       ps1 = &s1[5];
  20411.  25       ps1->charray[2] = 5; /* Note that this is ASCII 5 */
  20412.  26       (*ps1).charray[2] = 5; /* not '5'                 */
  20413.  27
  20414.  28       printf("%d\n", sizeof(ps1->charray));
  20415.  29       for (ps1 = &s1[0]; ps1 < &s1[HBOUND(s1)];
  20416.  30              ps1++) {
  20417.  31      cp = ps1->charray;
  20418.  32      while (cp < &ps1->charray[sizeof(
  20419.  33                                    ps1->charray)])
  20420.  34          *cp++ = '\0';
  20421.  35       }
  20422.  36   }
  20423.  
  20424.  Summary
  20425.  
  20426.  Although this article has focused specifically on structures and the
  20427.  different aspects   of having pointers to structures, accessing struct
  20428.  members, avoiding memory conflicts with arrays, and memory allocation, most
  20429.  of these concepts apply to unions as well. With the information presented
  20430.  here, added to the insights presented in the previous article, you now have
  20431.  a solid base of knowledge concerning the use of  structures. When you add to
  20432.  this the insights into unions, typedefs, and C declarations that you have
  20433.  been storing up from the past several issues, you are ready for some serious
  20434.  C programming.
  20435.  
  20436.  ────────────────────────────────────────────────────────────────────────────
  20437.  
  20438.  Volume 4 - Number 4
  20439.  
  20440.  ────────────────────────────────────────────────────────────────────────────
  20441.  
  20442.  
  20443.  Circumventing DOS Program Memory Constraints with an Overlay Manager
  20444.  
  20445.   Dan Mick
  20446.  
  20447.  In the current state of computing, both the PC end user and the programmer
  20448.  have an incredible array of software tools at their disposal. Thousands of
  20449.  terminate-and-stay-resident (TSR) utilities add timesaving, useful
  20450.  features to MS-DOS(R) and PC-DOS operating system machines, while
  20451.  integrated environments and multitasking shells extend the PC's power. But
  20452.  all these tools use memory, sometimes a good deal of it. Therefore,
  20453.  application programmers need to be certain their code is well designed,
  20454.  using small utility modules to decrease duplicated routines. Although such
  20455.  optimization can save a lot of code space, sometimes it isn't enough. In
  20456.  such cases, an overlay manager may be just the thing an application needs.
  20457.  
  20458.  The overlay is a fairly common concept for data space; the C programmer will
  20459.  be familiar with malloc and free to manage dynamic memory, as will the
  20460.  Pascal programmer with New and Free, Mark and Release. Overlaying may be
  20461.  thought of as a kind of dynamic code memory allocation.
  20462.  
  20463.  The basic concept of the overlay manager is that not every routine in a
  20464.  program needs to be loaded into memory at the same time. In a word
  20465.  processing program, for example, the printing output routines for a document
  20466.  will probably never be active while that document is being edited. Overlays
  20467.  make it possible for the printer routines to stay on disk until needed, at
  20468.  which point the printing code can be put in memory and the editing code can
  20469.  be put on disk (to be read later or to be discarded). This way, the word
  20470.  processor uses less memory. Another example is the printer driver code.
  20471.  Since only one printer is used at a time, only that printer driver is loaded
  20472.  at a time.
  20473.  
  20474.  Using Overlays
  20475.  
  20476.  The only justification for overlaying code is the decrease in resident code
  20477.  size, since speed will suffer as a result of disk loading times. But
  20478.  decreasing code size is important.
  20479.  
  20480.  No matter what the overlay scheme, the programmer's job is much easier if
  20481.  the code need not be modified greatly; in particular, the application should
  20482.  be designed as a "flat" application to aid in development and debugging,
  20483.  with the overlay management added late in the development cycle. (Debugging
  20484.  an overlaid program can really give you headaches.) Also, the decisions
  20485.  concerning how the program is overlaid are best delayed until late in the
  20486.  development cycle, since the complexity and size of routines may make a
  20487.  difference. If it's easy to add overlay management, it can be added late in
  20488.  the cycle without requiring a lot of editing to existing working code.
  20489.  
  20490.  TSRs can make life easier, as programmers and users realize. Editors,
  20491.  calendars, alarms, datebooks, help and documentation, communications, even
  20492.  games are nice to have available at the touch of a key, but they come at the
  20493.  expense of precious memory. It's not difficult to have two or three
  20494.  favorite utilities and find out there's no longer room for much editing or
  20495.  compilation on the system. Overlaying TSRs is a natural, since they're
  20496.  dormant most of the time; it's only when the TSR is active that any code
  20497.  is needed. At that point, the memory could be allocated from DOS and the
  20498.  code loaded; it could then be released when the TSR is deactivated.
  20499.  
  20500.  It's nice to be able to have some TSRs loaded while running other, more
  20501.  complex programs in which all the code is not needed. A word processor
  20502.  could be a desktop-publishing-style program that takes up 500Kb, leaving no
  20503.  room for anything else to be loaded. But if it were overlaid, it might use
  20504.  only 300Kb, leaving room for whatever else the user might desire.
  20505.  
  20506.  The speed of the application will suffer--but only when moving from one
  20507.  overlay to another. With good planning, the programmer can ensure that the
  20508.  swapping time is minimal, or perhaps even unnoticeable, buried in the time
  20509.  it takes a user to read the screen output from the last command. Even if the
  20510.  delay is noticeable, the program's smaller memory size may be worth the
  20511.  speed tradeoff.
  20512.  
  20513.  To alleviate some of the speed loss, the overlaid code can be stored in LIM
  20514.  EMS memory, allowing it to load nearly instantly. Expanded memory is ideal
  20515.  for this application, as it's not easy to actually run code from the EMS
  20516.  window (at least with older versions of EMS), but it's certainly easy to
  20517.  copy pages to and from the EMS window in the overlay manager. A disk cache
  20518.  in EMS can, however, serve the same purpose with far less effort.
  20519.  
  20520.  Early Approaches
  20521.  
  20522.  Programmers have been using different approaches to implement overlays for
  20523.  awhile. One early approach was built-in language statements, such as OVERLAY
  20524.  FUNCTION and OVERLAY SUBROUTINE. Mainframe FORTRAN compilers often had
  20525.  built-in statements for the programmer to direct the overlaying
  20526.  process. Another early approach was roll-your-own overlaying, as practiced
  20527.  by WordStar(R) (CP/M(R) and DOS) and Lotus(R) 1-2-3(R).
  20528.  
  20529.  The main disadvantages to the above two approaches are code complexity and
  20530.  the lack of transparency to the programmer. Extra work means extra
  20531.  places to introduce bugs, and overlay-system bugs can be hard to find.
  20532.  
  20533.  Other approaches to overlay management leave the application programmer
  20534.  to do what he or she will, and the operating system to do the work of
  20535.  swapping code. One approach, used on some early mainframes, is
  20536.  whole-process swapping, which allowed multiple applications to share the
  20537.  computer's resources by swapping the entire program to secondary storage and
  20538.  gave control of the machine to the next program in line.
  20539.  
  20540.  Demand-paged or segmented virtual-memory management, used more often today,
  20541.  break the code space into smaller units than processes or programs, called
  20542.  pages (or segments), and swap parts of processes rather than the entire
  20543.  process. This allows finer granularity in swapping code space than
  20544.  whole-process swapping, therefore using memory more efficiently and
  20545.  involving less disk I/O.
  20546.  
  20547.  There are some disadvantages, however, to allowing the operating
  20548.  system to do the swapping work. For one thing, each process may suffer
  20549.  more memory-management overhead during its execution since all the pages
  20550.  for the entire process may not be present at once. In addition, the
  20551.  operating system has to manage the swapping for all processes, so it must
  20552.  approach things in the general sense, trying to manage memory well for an
  20553.  "average" application--which, of course, doesn't exist. The application
  20554.  itself will know more about which code needs to be coresident and which does
  20555.  not. Moreover, the operating system makes its swapping decisions at
  20556.  times unrelated to program execution. The program may know, for
  20557.  instance, that it is now done with the initialization code forever. The
  20558.  operating system has no way of telling that the initialization code is
  20559.  used only once.
  20560.  
  20561.  Current Approaches
  20562.  
  20563.  There are several current solutions available for PC programmers who
  20564.  want to overlay their applications. Phoenix Technologies, Ltd., sells a
  20565.  product called PLINK that works with Microsoft(R) OBJ files, allowing
  20566.  incredible flexibility in overlaying both code and data. Also, PLINK is a
  20567.  nonstandard linker; this may or may not be a bad thing, but it certainly
  20568.  increases the number of manuals on your desk.
  20569.  
  20570.  Microsoft C and Microsoft FORTRAN optimizing compilers provide overlaying
  20571.  capability through the Microsoft Overlay Linker (LINK) and the overlay
  20572.  managers supplied with the language run-time libraries. But the Microsoft
  20573.  Macro Assembler (MASM) programmer cannot use LINK's overlaying
  20574.  features without those languages. Also, the Microsoft overlay manager and
  20575.  linker do not support languages that Microsoft doesn't offer, such as C++,
  20576.  Modula-2, and PL/I, or even Microsoft QuickBASIC.
  20577.  
  20578.  LINK
  20579.  
  20580.  LINK does provide overlaying capabilities that programmers can make
  20581.  use of without developing in Microsoft C or FORTRAN. LINK provides the
  20582.  most basic form of overlaying. One section of the program is defined as the
  20583.  overlay area, and there may be several different code overlay segments (but
  20584.  fewer than 256, because the selector is 1 byte). Each overlay segment is
  20585.  made up of one or more modules (object files) that live, one at a time, in
  20586.  the overlay area. The overlay manager is expected to install itself as an
  20587.  interrupt routine (the linker changes the entry point of the program to a
  20588.  location in the overlay manager so the manager has a chance to initialize
  20589.  itself before the main program code is called). The linker then translates
  20590.  all overlay routine calls to software interrupts, as described below, to
  20591.  allow the overlay manager to load the overlay code from disk before the
  20592.  control transfer takes place.
  20593.  
  20594.  There is only one overlay segment present in the overlay area at once, but
  20595.  it may involve object code from more than one module. For example, the
  20596.  command line
  20597.  
  20598.  LINK main+(p1+p2)+(p3) ;
  20599.  
  20600.  causes LINK to make MAIN.EXE, which consists of a resident portion main, and
  20601.  two overlay segments, one made up of code from modules p1 and p2, and one
  20602.  made up of code from module p3.
  20603.  
  20604.  An important point to note is that in order for this to work, modules p1,
  20605.  p2, and p3 need to have class names ending in "CODE", and each needs to be a
  20606.  different segment than the segment defined in MAIN. In the examples shown
  20607.  here, MASM's .LARGE directive and separate source files take care of this,
  20608.  but it's something to be careful of.
  20609.  
  20610.  The far code model must be used for the overlay manager; that is, code must
  20611.  be located at far addresses and use far calls to transfer control. This
  20612.  enables the overlay linker to replace the far call (5 bytes) with a software
  20613.  interrupt instruction (2 bytes), an overlay number (1 byte), and an offset
  20614.  into the overlay area for the call (when more than one entry point into the
  20615.  overlay is provided). For example, a CALL MY_ROUTINE might be replaced with
  20616.  INT 3FH (which is the default overlay-interrupt-handler number), DB 01
  20617.  (signifying the first overlay), and DW 0100 (representing an offset of
  20618.  100H into the beginning of the overlay area as the call
  20619.  destination). LINK replaces only those calls that refer to overlaid
  20620.  code. Interestingly, if you examine an overlay-linked program with SYMDEB,
  20621.  the overlay calls are disassembled as INT 3F; <overlay number> <overlay
  20622.  offset> just as described. SYMDEB is at least partially overlay aware. The
  20623.  Microsoft CodeView(R) debugger (referred to herein as CodeView) even has
  20624.  hooks so that local symbols for overlay procedures are properly mapped in.
  20625.  
  20626.  Note that this method of calling the overlay manager will not properly
  20627.  handle functions that are called through pointers. If you attempt to use the
  20628.  simple overlay manager presented here, and call an overlaid function
  20629.  through a function pointer, LINK will not replace the call with an INT 3FH
  20630.  and the overlay manager will never have the chance to load the routine.
  20631.  Needless to say, this will have disastrous effects.
  20632.  
  20633.  LINK also structures the EXE file differently for overlaid executables. In
  20634.  short, there is a separate EXE header for each overlay segment, with
  20635.  fields specific to the overlay routine that follows it and a
  20636.  code-segment-only data block. I'll describe this in more detail below.
  20637.  
  20638.  Global Variables
  20639.  
  20640.  LINK inserts several global variables for the overlay manager's use. I'll
  20641.  list here the ones I've examined.
  20642.  
  20643.  ■    OVERLAY_AREA, OVERLAY_END. Two segment names defined by the linker at
  20644.  the very end of the CODE-class segments. OVERLAY_AREA is defined at the
  20645.  beginning of the overlay area. The end of the overlay area (first
  20646.  available paragraph after the largest overlay) is marked by OVERLAY_END.
  20647.  
  20648.  ■    $$CGSN. A variable containing a count of the number of separate
  20649.  overlay segments. It's not used in our overlay manager, which simply tries
  20650.  to find the requested overlay until there are no more in the file.
  20651.  
  20652.  ■    $$COVL. Contains the same value as $$CGSN. $$COVL is a limit on the
  20653.  number of overlays, whereas $$CGSN is used as a size parameter for the
  20654.  $$MPGSNBASE array.
  20655.  
  20656.  ■    $$EXENAM. The name of the executable file--base name and extension
  20657.  (which will be EXE). It's not used in my overlay manager, since the full
  20658.  executable name is available from DOS in the environment built for the
  20659.  program.
  20660.  
  20661.  ■    $$INTNO. The software interrupt number that is assigned for
  20662.  overlay manager operations. The
  20663.  /OVERLAYINTERRUPT switch on the LINK command line allows this to be
  20664.  changed from its default of 3FH. Since the calls will use this number, the
  20665.  overlay manager should use it to pick the interrupt vector to take over.
  20666.  
  20667.  ■    $$MAIN. The original program entry point. This is the entry point
  20668.  that would be used if the program were not overlaid. Once the program is
  20669.  overlaid, the entry is changed to $$OVLINIT (see below). The overlay manager
  20670.  initialization code can branch to $$MAIN once it has initialized itself and
  20671.  taken over the $$INTNO interrupt vector.
  20672.  
  20673.  ■    $$MPGSNBASE. An array of $$CGSN words, each containing the segment
  20674.  address of an overlay. $$MPGSNBASE[0] contains the segment address of the
  20675.  resident section of the program (that is, the PSP address + 10H), and the
  20676.  entries 1-$$CGSN contain OVERLAY_SEGMENT. $$MPGSNBASE is used to find the
  20677.  segment address of the requested overlay segment.
  20678.  
  20679.  ■    $$MPGSNOVL. An array of $$CGSN bytes, each one of which contains an
  20680.  overlay number corresponding to an entry in $$MPGSNBASE. $MPGSNOVL[0], the
  20681.  entry for the resident section, is null and each entry thereafter is equal
  20682.  to its index.
  20683.  
  20684.  ■    $$MPOVLLFA. A cache for overlay file offsets in the Microsoft overlay
  20685.  manager. There are as many doubleword entries as overlays, and
  20686.  $$MPOVLLFA[n] is either zero (as initialized) or set to the location in the
  20687.  file for the overlay numbered n. Although it could speed up large
  20688.  applications significantly, I decided not to use this in my overlay
  20689.  manager. For small tests the time spent searching the executable is minimal.
  20690.  
  20691.  ■    $$OVLBASE. A symbol equal to the address of OVERLAY_SEGMENT. This seems
  20692.  to be a conveniently initialized, alternate way of finding out  where to
  20693.  load the overlay. I used its value instead of OVERLAY_SEGMENT; either would
  20694.  be appropriate.
  20695.  
  20696.  ■    $$OVLINIT. The address of the new entry point of the program after
  20697.  overlaying. The overlay manager should define its entry point as $$OVLINIT
  20698.  and perform a branch to $$MAIN after initialization. LINK sets $$MAIN to
  20699.  the original code's entry point.
  20700.  
  20701.  EXE File Structure
  20702.  
  20703.  As mentioned above, the EXE file from an overlay linker session is
  20704.  structured a bit differently from a normal DOS executable file. Each
  20705.  overlay segment (those modules that are contained within one set of
  20706.  parentheses make up an overlay segment) has its own EXE header. The header
  20707.  looks just like the normal EXE header, omitting some data (which is used
  20708.  only by the DOS loader) that applies only to the program as a whole. Initial
  20709.  stack settings, entry point, and so on, appear only in the initial header.
  20710.  The overlay number, however, changes in each subsequent header, and the
  20711.  length fields change as needed in order to find the next section. See the
  20712.  file EXEHDR.H for a full description of the structure. Of course, as with
  20713.  any LINK session, it's a good idea to create a MAP file with the /M option
  20714.  and examine the result.
  20715.  
  20716.  Overlay Manager
  20717.  
  20718.  It's possible, knowing the hooks that LINK provides, to write a simple
  20719.  overlay manager for languages that do not include support, particularly
  20720.  MASM. OVRMGR.ASM (see Figure 1) and RDOVLY.C (see Figure 2) make up an
  20721.  overlay manager that can be used with MASM programs and non-Microsoft
  20722.  languages.
  20723.  
  20724.  OVRMGR.ASM takes care of the overlay manager initialization and also
  20725.  contains the shell of the interrupt handler code. RDOVLY.C does all the
  20726.  dirty work. It actually reads the EXE file and reads in the overlay code at
  20727.  the address determined by OVRMGR.ASM. C code is much easier to write,
  20728.  especially at this level, and certainly easier to debug. Also, the C
  20729.  run-time facilities for file handling take a lot of load off the programmer
  20730.  for development effort.
  20731.  
  20732.  For those of you who have a Microsoft C compiler, the C function was
  20733.  translated into an assembly file, RDOVLY.ASM (see Figure 3), and the
  20734.  pertinent C run-time functions were added as assembly definitions in
  20735.  SUPPORT.ASM (see Figure 4). The code is commented and should be
  20736.  self-explanatory, but the following offers a rundown.
  20737.  
  20738.  OVRMGR assumes control at $$OVLINIT and proceeds to install ovly_int as the
  20739.  new interrupt service routine for the INT $$INTNO handler, saving the old
  20740.  vector for completeness. It's not restored by the code in its present form.
  20741.  It would be simple enough to define an add-on to your language's exit
  20742.  clean-up routines that restores the vector from old_ovint, where it's
  20743.  stored. Once the initialization is complete, OVRMGR performs a far jump to
  20744.  $$MAIN, the original program entry point.
  20745.  
  20746.  When an overlay is called, OVRMGR again assumes control at ovly_int.
  20747.  First, the handler checks to see if it's already in the middle of an
  20748.  overlay call; if it is, it prints an error message and terminates the
  20749.  program with error code 41H. (That is, this example manager does not allow
  20750.  an overlay to call another overlay or itself.) If the overlay manager is not
  20751.  currently active, the overlay number and offset are obtained from the
  20752.  original code stream; the segment address for the overlay area is obtained
  20753.  from the $$MPGSNBASE array, and read_overlay_section is called to read in
  20754.  the overlay. On return, if there was an error reading the file, OVRMGR exits
  20755.  with error code 42H; otherwise, the overlay may be called. I will go into
  20756.  more detail on this below.
  20757.  
  20758.  The read_overlay_section routine is reasonably straightforward. First, the
  20759.  executable file is opened, using the executable_name variable that was set
  20760.  by the original program. The variable executable_name is an ASCIIZ string (a
  20761.  C-style string) containing the full pathname of the executable file; this
  20762.  is easily obtained from a C program and is located just after the
  20763.  environment passed to any process.
  20764.  
  20765.   Many languages have argument-string arrays like C's argv[ ] array, so I
  20766.  won't dwell on how to create executable_name. It must, however, be defined
  20767.  by the program that uses the overlay manager. The example assembly main
  20768.  routine, _main, calls a procedure named fill_exe_name to initialize
  20769.  executable_name (it's allocated in OVRMGR.ASM). Be careful if you try to run
  20770.  this under SYMDEB. I found out, much to my chagrin, that SYMDEB eats the
  20771.  path specifiers from the front of the executable name stored in the
  20772.  environment. When running OVASM.EXE from the DOS command line, the whole
  20773.  path is placed in the environment.
  20774.  
  20775.  After opening the file that was named by executable_name,
  20776.  read_overlay_section determines the file size for later use. The lseek
  20777.  function is used to find the file size, in contrast to the expected method
  20778.  of using the file length or tell functions. Once I decided to prototype in
  20779.  C, I tried to limit the number of run-time calls made (since I needed to
  20780.  program them in assembly language anyway).
  20781.  
  20782.  Following this, the file pointer position is returned to the beginning of
  20783.  the file, and read_overlay_section begins an endless loop to search for the
  20784.  requested overlay. If there is an error at any point during the search,
  20785.  read_overlay_section( ) prints an error message using the custom routine
  20786.  errputs, closes the executable, and returns with a nonzero error code (in
  20787.  this case, 1). Errors include read errors, unrecognizable information in
  20788.  the executable file, and overlay not found. Presence of the file that is
  20789.  named in executable_name is assumed. If the file is not present (for
  20790.  example, if it's on a floppy that's been removed), however, the read of the
  20791.  executable header will fail, returning an error.
  20792.  
  20793.  When read_overlay_section finds the requested overlay, it reads that section
  20794.  of the file into the area pointed to by the ovarea parameter. Then it must
  20795.  relocate the code. Each segment reference in the code must be offset by
  20796.  the program load address. This is the same relocation performed by
  20797.  COMMAND.COM when it loads the program initially, but only for the root
  20798.  section--the overlay manager is responsible for relocating the loaded
  20799.  overlay. In order to offset each reference in the program by the load
  20800.  address, $$OVLINIT sets the variable pspaddr for use during relocation. The
  20801.  program load address is always the address of the PSP plus 10H, since the
  20802.  PSP is 100H bytes long. The variable, pspaddr, and the relocation table
  20803.  located in the file header are used by read_overlay_section in order to
  20804.  locate the overlay.
  20805.  
  20806.  The read_overlay_section handle  does the relocation in the while
  20807.  (eh.num_relocs) loop; it continues until all relocation entries in the
  20808.  table have been used. Each relocation fix-up is done by adding the segment
  20809.  part of the relocation entry to the segment part of the load address
  20810.  (pspaddr + 0x10). The resultant far pointer is the address of the target
  20811.  word of the relocation. The pointer points into a code at a segment
  20812.  reference that must be fixed up. Once the pointer is formed,
  20813.  read_overlay_section adds the program load address (pspaddr + 0x10) to this
  20814.  target word, changing 0-relative segment references to actual absolute
  20815.  segment references. Notice the buffering of relocation entries; it is one of
  20816.  the reasons I decided to write the routine in C.
  20817.  
  20818.  Once the relocation is finished, the executable file is closed and control
  20819.  returns to OVRMGR.ASM with a return code of 0. (If there was an error in the
  20820.  load, as explained above, the return code would have been 1 and ovly_int
  20821.  would print an error message and die.) If there was no error, the address of
  20822.  return_from_ov is pushed on the stack so that the RETF from the overlaid
  20823.  routine will return to return_from_ov (thus explaining the choice of
  20824.  label).
  20825.  
  20826.  Next, the address of the overlaid routine itself is pushed onto the stack
  20827.  and a RETF is executed, causing a jump to the overlaid, relocated routine.
  20828.  I did this mainly because I'm already pushing things; a jump through a far
  20829.  pointer would have been just as easy. When the overlaid routine is done,
  20830.  it executes a RETF (remember that we are working strictly in far code).
  20831.  Control transfers to return_from_ov, which cleans up, resets the flag that
  20832.  indicates that we're no longer in the overlay manager, and returns control
  20833.  to the mainline program, just after the call to the overlaid routine.
  20834.  
  20835.  SUPPORT.ASM contains the assembly functions that supplant the C run-time
  20836.  functions used in RDOVLY.ASM: errputs, myopen, myclose, mylseek, and myread.
  20837.  OVRMGR.ASM contains the overlay initialization and interrupt handler and
  20838.  RDOVLY.C translates to RDOVLY.ASM, completing the simple overlay
  20839.  management package.
  20840.  
  20841.  Test Code
  20842.  
  20843.  Several test routines are provided with the overlay manager, both in
  20844.  assembly language and in C. The files OV.C, P1.C, and P2.C (see Figure 5)
  20845.  comprise a simple test, and OVASM.ASM, P1ASM.ASM, and P2ASM.ASM (see
  20846.  Figure 6) are similar test routines in assembly language. If you have
  20847.  Microsoft C Version 5.1 and MASM Version 5.1, you can remake the entire
  20848.  package in C or assembly language using MAKE and the sample make files, OV
  20849.  and OVASM (see Figure 7). Of course MASM and LINK must be available in
  20850.  your DOS executable path, you must have a version of LINK that supports
  20851.  overlaying, and so on.
  20852.  
  20853.  The RDOVLY.C source file, which eventually becomes the RDOVLY.ASM assembly
  20854.  language source, is processed by a small utility called SMERGE (see
  20855.  SMERGE.C listed in Figure 8), which merges the C source into the /Fa
  20856.  assembly language output from Microsoft C. SMERGE also removes an annoying
  20857.  external definition, an EXTRN _acrtused:ABS, which is issued by the
  20858.  compiler. Reasonably portable source for SMERGE.C is provided for those
  20859.  with C compilers.
  20860.  
  20861.  Limitations
  20862.  
  20863.  The code given here isn't sufficient for a really full-featured overlay
  20864.  manager. For one thing, the error handling could be improved. For instance,
  20865.  there is currently no provision for handling the case in which a floppy
  20866.  disk containing the executable file is removed during program execution.
  20867.  Granted, most PC users have hard disks, and the programs that will benefit
  20868.  from overlay management often live on those hard disks, but a specific
  20869.  error handler could be added for a really bulletproof package. The Microsoft
  20870.  overlay manager, for instance, requests either the floppy disk or a new
  20871.  pathname for the executable file.
  20872.  
  20873.  A more serious limitation is that only one overlay can be active at a time.
  20874.  This is due to the way information is stored in the overlay manager and the
  20875.  way returns are processed. It would be a fairly simple matter to modify
  20876.  OVRMGR.ASM to support an arbitrary number of overlays. The Microsoft C
  20877.  overlay manager supports up to 128 nested overlay calls. I'll leave this
  20878.  as an exercise for the investigative and adventurous reader.
  20879.  
  20880.  Microsoft notes, in the MASM documentation, that CodeView is now compatible
  20881.  with the overlay manager; the manual even mentions that overlaying may
  20882.  be necessary to get a large application to run under CodeView. In fact, the
  20883.  Microsoft overlay manager contains special hooks into CodeView and DEBUG
  20884.  so that special calls are made to both in order to map in new symbols.
  20885.  
  20886.  This overlay manager clearly can't support undocumented features, but
  20887.  I've found that debugging is still possible--it's just not as
  20888.  convenient--by using a symbolic debugger's capabilities for loading
  20889.  symbol files after the program has been loaded. Just rip out the sections
  20890.  of the MAP file that relate to the overlaid routines. Since data segments
  20891.  aren't overlaid, the problem usually isn't too limiting; data labels are the
  20892.  things I most often want to see in the disassembly. But it's worth
  20893.  noting that debugging an overlaid program can be interesting.
  20894.  
  20895.  Using overlay management offers one solution to the problem of limited
  20896.  code memory. This article has offered some insight into programming
  20897.  techniques for limited code memory by discussing Microsoft's solution
  20898.  for far code program development in addition to providing MASM
  20899.  programmers with a way to use it. While this particular overlay manager
  20900.  may not be useful to you, a modification along the lines suggested above
  20901.  might be useful. Overlay management is definitely an area worth
  20902.  exploring.
  20903.  
  20904.  
  20905.  Programming Assembly Language Routines in C
  20906.  
  20907.  Programming in assembly language has undeniable advantages; the low-level
  20908.  control, raw speed, and small code size just can't be equaled by a
  20909.  high-level language. But assembly programming can be terrible drudgery,
  20910.  prototyping in assembly can be painful (even for experienced assembly
  20911.  language programmers), and routines that involve array access, looping with
  20912.  complex termination conditions, or file I/O can involve lots of error-prone
  20913.  code. For these reasons, I decided to write the initialization and shell of
  20914.  the overlay interrupt handler in MASM and use Microsoft C for the main body
  20915.  of the routine. The result is the function read_overlay_section contained in
  20916.  the file RDOVLY.C.
  20917.  
  20918.  Since I also wanted to present an overlay manager for assembly language
  20919.  programmers, I proceeded to rewrite C portions of the routines in
  20920.  assembly--until it occurred to me that the Microsoft C /Fa option (to
  20921.  generate a MASM-compatible assembly language output file) could write the
  20922.  code for me, with some help.
  20923.  
  20924.  The first thing I had to do was rewrite the run-time routines I needed from
  20925.  the C run-time library in assembly language. This was easy with a bit of
  20926.  advance planning. For instance, since I was aware of how easily I could
  20927.  write an lseek function using DOS function call 42H, I used that function
  20928.  instead of the C function tell to get the current file position. I also knew
  20929.  I would need lseek in other places, and the proper form of lseek returns the
  20930.  same value as tell. To make the rewriting job easier, I limited file I/O to
  20931.  level 1 I/O, which is most like DOS file I/O. The small routines in
  20932.  SUPPORT.ASM are those that take the place of the C run-time routines as well
  20933.  as a special print to stderr function (errputs).
  20934.  
  20935.  Switches for the Microsoft C compiler driver, CL, are contained in both
  20936.  example make files. /c instructs CL to compile the file but doesn't go on to
  20937.  the link step, and /Fa and /Fonul cause the assembly language output to
  20938.  appear while sending any OBJ output to the NUL device, effectively
  20939.  discarding it. /AL causes the compiler to use large-model coding; that is,
  20940.  far pointers for data and code. LINK requires this when overlaying code. /Gs
  20941.  suppresses generation of calls to _chkstk, the stack-overflow-check routine,
  20942.  since this is a hidden run-time routine that isn't duplicated in my assembly
  20943.  code. /Zl suppresses the default library search, usually written to the OBJ
  20944.  file. This is probably not needed here, since I'm generating ASM files
  20945.  directly, but I'm careful. Finally, since I planned on including the
  20946.  assembly output, I used /Od to suppress all optimizations, so the output
  20947.  code would be clearer.
  20948.  
  20949.  Since the ASM output contains references to the original C source line, I
  20950.  decided to merge the C source and the compiler-generated assembly source to
  20951.  make it easier to see what's happening in the assembly output. SMERGE.C is a
  20952.  program written to automate the merging process, replacing comments of the
  20953.  form
  20954.  
  20955.  ; Line <n>
  20956.  
  20957.  with the actual nth line of the C source. When I discovered that CL inserts
  20958.  an extra data declaration for a variable _acrtused (whose purpose I never
  20959.  discovered), I decided to remove that declaration with SMERGE as well. Thus,
  20960.  CL and SMERGE together make a clearer, commented assembler file.
  20961.  
  20962.  I didn't hand optimize anything in the result. It's possible that
  20963.  performance could be improved by doing so, and certainly more mnemonic
  20964.  labels could be added by hand. A first glance at the code generated by CL
  20965.  indicated that it wasn't unclear, so I decided to leave well enough alone.
  20966.  
  20967.  The experiment worked well with RDOVLY.C. I was able to write a functional
  20968.  overlay manager in an afternoon, tweak its performance easily by using C,
  20969.  and then write an assembly-language-only version in just a few more hours.
  20970.  Had I started writing in assembly language, changing the overlay section
  20971.  reader's error handling and buffering of relocation records would have been
  20972.  much more difficult. But C is especially suited for such design modification
  20973.  and is close enough kin to assembly to generate reasonably efficient output.
  20974.  Give prototyping in C a serious try; you'll like it.
  20975.  
  20976.  Figure 1
  20977.  
  20978.  .model LARGE
  20979.              extrn  $$INTNO:byte
  20980.              extrn  $$MPGSNBASE:word
  20981.              extrn  $$MPGSNOVL:byte
  20982.              extrn  $$MPOVLLFA:word
  20983.              extrn  $$OVLBASE:byte
  20984.  
  20985.              extrn  $$MAIN:far
  20986.              extrn  _read_overlay_section:far
  20987.              extrn  _errputs:far
  20988.  
  20989.              .data
  20990.  _pspaddr    dw     ?     ;for relocation with read_overlay_section
  20991.  old_ovint   dw     ?     ;old overlay interrupt offset
  20992.              dw     ?     ;and segment
  20993.  
  20994.  _executable_name   db    80 dup (0)     ;space for full pathname
  20995.  
  20996.  cantmap     db     'Can''t map overlay!...exiting',13,10,0
  20997.  reentry     db     'Already mapping an overlay!...exiting',13,10,0
  20998.  
  20999.  
  21000.              .code
  21001.  
  21002.              public  $$OVLINIT
  21003.              public  _executable_name
  21004.              public  _pspaddr
  21005.              public  ax_save,bx_save,cx_save,es_save,ret_ip,ret_cs
  21006.              public  req_ov,ov_ofs,ov_seg,in_ovly_mgr,ovly_int
  21007.              public  return_from_ov
  21008.  
  21009.  ax_save     dw     ?     ;save area for used registers
  21010.  bx_save     dw     ?
  21011.  cx_save     dw     ?
  21012.  es_save     dw     ?
  21013.  
  21014.  ret_ip      dw     ?     ;original return address
  21015.  ret_cs      dw     ?     ;(from call to overlaid routine)
  21016.  
  21017.  req_ov      db     ?     ;requested overlay number
  21018.  ov_ofs      dw     ?     ;address to call after mapping
  21019.  ov_seg      dw     ?
  21020.  
  21021.  in_ovly_mgr        db    0     ;flag to indicate already mapping
  21022.  
  21023.  $$OVLINIT   label  far
  21024.              push   ax
  21025.              push   bx
  21026.              push   dx
  21027.              push   es
  21028.              push   ds          ;need to use ds:dx
  21029.              mov    ax,DGROUP
  21030.              mov    ds,ax
  21031.  
  21032.              mov    ax,es       ;initialize _pspaddr
  21033.              mov    _pspaddr,ax
  21034.  
  21035.              mov    al,[$$INTNO]      ;get interrupt number
  21036.              mov    ah,35h      ;get overlay number interrupt vector
  21037.              int    21h
  21038.  
  21039.              mov    old_ovint,bx
  21040.              mov    old_ovint+2,es
  21041.  
  21042.              mov    bx,cs
  21043.              mov    ds,bx
  21044.              mov    dx,offset ovly_int      ;new interrupt
  21045.              mov    ah,25h
  21046.              int    21h         ;install new int handler
  21047.              pop    ds
  21048.              pop    es
  21049.              pop    dx
  21050.              pop    bx
  21051.              pop    ax
  21052.  
  21053.              jmp    $$MAIN      ;go to mainline code
  21054.  
  21055.  
  21056.  ;**
  21057.  ;** INTERRUPT HANDLER
  21058.  ;**
  21059.  
  21060.  ovly_int    proc   far
  21061.              cmp    cs:in_ovly_mgr,0  ;are we not here already?
  21062.              je     ok                ;right
  21063.  ; Error if we're already in overlay manager!
  21064.              mov    ax,offset reentry       ;error message
  21065.              push   ds
  21066.              push   ax
  21067.              call   _errputs
  21068.              add    sp,4
  21069.              mov    ax,4c41h    ;exit with error code 41h
  21070.              int    21h
  21071.  
  21072.  ok:         mov    cs:in_ovly_mgr,1      ;note that we're here
  21073.              mov    cs:ax_save,ax
  21074.              mov    cs:bx_save,bx
  21075.              mov    cs:cx_save,cx
  21076.              mov    ax,es
  21077.              mov    cs:es_save,ax
  21078.  
  21079.              pop    bx          ;bx = return ip
  21080.              push   bx
  21081.              add    bx,3              ;adjust for extra stuff
  21082.              mov    cs:[ret_ip],bx    ;save it
  21083.              pop    bx                ;get original return address back
  21084.  
  21085.              pop    ax                ;return cs
  21086.              mov    cs:[ret_cs],ax    ;save it
  21087.              mov    es,ax             ;es:bx -> bytes after INT
  21088.  
  21089.              pop    ax                ;flags, discard
  21090.  
  21091.              xor    ah,ah
  21092.              mov    al,byte ptr es:[bx] ;get requested overlay number
  21093.              inc    bx                      ;move to next item
  21094.              mov    byte ptr cs:req_ov,al   ;save it
  21095.              mov    cx,word ptr es:[bx] ;get offset in overlay segment
  21096.              mov    cs:[ov_ofs],cx          ;save it
  21097.  
  21098.              push   ax                      ;p3 for C function
  21099.              mov    bx,ax
  21100.              shl    bx,1                    ;* 2
  21101.              add    bx,offset $$MPGSNBASE   ;add to base of
  21102.                                             ;segment table
  21103.              mov    bx,[bx]
  21104.              mov    ov_seg,bx
  21105.              push   bx                      ;push the segment for p2
  21106.              mov    ax,0                    ;p2, low word (0)
  21107.              push   ax
  21108.              call   _read_overlay_section   ;call the pup!
  21109.              add    sp,6                    ;get rid of parms
  21110.              cmp    ax,0                    ;error return?
  21111.              je     no_error                ;no
  21112.  
  21113.  ; Error if can't map this overlay!
  21114.              mov    ax,offset cantmap     ;error message
  21115.              push   ds
  21116.              push   ax
  21117.              call   _errputs
  21118.              add    sp,4
  21119.              mov    ax,4c42h              ;exit with error code 42h
  21120.              int    21h
  21121.  
  21122.  no_error:   push   cs
  21123.              mov    ax,offset return_from_ov    ;trick up return
  21124.                                                 ;to our code
  21125.              push   ax
  21126.              mov    ax,seg $$OVLBASE  ;set up call to overlay code
  21127.              push   ax
  21128.              mov    ax,ov_ofs
  21129.              push   ax
  21130.              retf                ;jump to overlay code...
  21131.  
  21132.  return_from_ov:                 ;..and come back here
  21133.              mov    ax,ret_cs    ;restore original return address
  21134.              push   ax
  21135.              mov    ax,ret_ip
  21136.              push   ax
  21137.              mov    ax,cs:es_save    ;and original reg values
  21138.              mov    es,ax
  21139.              mov    ax,cs:ax_save
  21140.              mov    bx,cs:bx_save
  21141.              mov    cx,cs:cx_save
  21142.              mov    cs:in_ovly_mgr,0      ;we're not here anymore
  21143.  
  21144.              ret
  21145.  ovly_int    endp
  21146.  
  21147.              end
  21148.  
  21149.  Figure 2
  21150.  
  21151.  #include "exehdr.h"
  21152.  
  21153.  /* externally-defined full pathname of executable */
  21154.  
  21155.  #define min(a,b) ((a) < (b)) ? (a) : (b)
  21156.  #define max(a,b) ((a) < (b)) ? (a) : (b)
  21157.  #define OPENMODE_RO 0
  21158.  #define MK_FP(a,b) ((void far *)( ((unsigned long)(a) << 16) |
  21159.                                     (unsigned)(b) ))
  21160.  
  21161.  extern char executable_name[];
  21162.  extern int pspaddr;
  21163.  
  21164.  void errputs(char far *s);
  21165.  long mylseek(int handle, long offset, int type);
  21166.  int myread(int handle, void far *buf, int len);
  21167.  int myopen(char far *name, int mode);
  21168.  int myclose(int handle);
  21169.  
  21170.  
  21171.  int read_overlay_section(
  21172.  void far *ovarea,              /* buffer into which to read him */
  21173.  int requested_overlay_number   /* number of overlay to read */
  21174.  )
  21175.  
  21176.  #define RELOC_BUF_SIZE 32
  21177.  {
  21178.      struct reloc_entry_ reloc_buf[RELOC_BUF_SIZE];
  21179.      struct reloc_entry_ *reloc_ptr;
  21180.      int reloc_chunk;
  21181.      unsigned far *target_ptr;
  21182.      struct exehdr_ eh;
  21183.      int i;
  21184.      long startpos, nextpos, filesize;
  21185.      long sectionsize, imagesize;
  21186.      int executable_handle;
  21187.  
  21188.      /* open executable */
  21189.      executable_handle = myopen(executable_name,OPENMODE_RO);
  21190.  
  21191.      /* determine file size */
  21192.      filesize = mylseek(executable_handle,0L,2);
  21193.  
  21194.      /* search from beginning of file */
  21195.      mylseek(executable_handle,0L,0);
  21196.  
  21197.      while (1) {
  21198.          /* use mylseek() to avoid calling runtime functions */
  21199.          startpos = mylseek(executable_handle,0L,1);
  21200.          if (myread(executable_handle,&eh,sizeof(eh)) != sizeof(eh)) {
  21201.              errputs("Something's wrong...can't read EXE header\r\n");
  21202.              myclose(executable_handle);
  21203.              return(1);
  21204.      }
  21205.  
  21206.      if (eh.M_sig != 'M' || eh.Z_sig != 'Z') {
  21207.      errputs("Found non-EXE signature!\r\n");
  21208.              myclose(executable_handle);
  21209.      return(1);
  21210.      }
  21211.          if (eh.remain_len == 0)
  21212.              sectionsize = (long)eh.page_len * 512;
  21213.          else
  21214.              sectionsize = (long)(eh.page_len - 1) * 512 +
  21215.                                   eh.remain_len;
  21216.  
  21217.          if (eh.overlay_number == requested_overlay_number) {
  21218.              /* found ours...load and fix up */
  21219.  
  21220.              /* move to executable image */
  21221.              mylseek(executable_handle, startpos + eh.hsize * 16, 0);
  21222.  
  21223.              myread(executable_handle, ovarea, (int)(sectionsize -
  21224.                                                      eh.hsize * 16));
  21225.  
  21226.              /* fix up relocations in the loaded overlay */
  21227.  
  21228.              mylseek(executable_handle,startpos +
  21229.                      (long)eh.first_reloc,0);
  21230.              while (eh.num_relocs) {
  21231.                  reloc_chunk = min(RELOC_BUF_SIZE,eh.num_relocs);
  21232.                  myread(executable_handle,reloc_buf,reloc_chunk *
  21233.                         sizeof(struct reloc_entry_));
  21234.                  eh.num_relocs -= reloc_chunk;
  21235.                  for (i = 0; i < reloc_chunk; i++) {
  21236.                      reloc_ptr = reloc_buf + i;
  21237.                      target_ptr = MK_FP(pspaddr + 0x10 +
  21238.                                         reloc_ptr->segment,
  21239.                                         reloc_ptr->offset);
  21240.                      *target_ptr += pspaddr + 0x10;
  21241.                  }
  21242.              }
  21243.              myclose(executable_handle);
  21244.              return 0;
  21245.          } else {
  21246.      nextpos = startpos + sectionsize;
  21247.              /* round up to 512-byte bound */
  21248.              nextpos = (nextpos + 511L) & ~511L;
  21249.      if (nextpos >= filesize) {
  21250.                  myclose(executable_handle);
  21251.      return 1;
  21252.              }
  21253.      mylseek(executable_handle,nextpos,0);
  21254.          }
  21255.      }
  21256.      myclose(executable_handle);
  21257.      return 1;
  21258.  }
  21259.  
  21260.  Figure 4
  21261.  
  21262.  .model  LARGE,C
  21263.              .code
  21264.  
  21265.              public errputs
  21266.  errputs     proc   far C uses ax bx cx di ds es, string:ptr
  21267.              les    di,string       ;es:di -> string
  21268.              push   di              ;save original offset
  21269.              mov    al,0            ;terminator byte
  21270.              mov    cx,0100h        ;length to examine for null
  21271.              repne  scasb           ;look for it
  21272.              pop    ax              ;ax = orig pointer
  21273.              sub    di,ax
  21274.              dec    di              ;di = length
  21275.              mov    cx,di
  21276.              mov    bx,2            ;stderr
  21277.              mov    dx,ax           ;buffer address
  21278.              mov    h,40h           ;write to file
  21279.              int    21h
  21280.              ret
  21281.  errputs     endp
  21282.  
  21283.  
  21284.  mylseek     proc   far C uses bx cx,handle:WORD,ofs:DWORD,whence:WORD
  21285.              public mylseek
  21286.              mov    ax,whence
  21287.              mov    ah,42h
  21288.              mov    bx,handle
  21289.              push   es
  21290.              les    dx,ofs
  21291.              mov    cx,es
  21292.              pop    es
  21293.              int    21h             ;result left in dx:ax, nicely
  21294.              jnc    lseekdone
  21295.              mov    ax,-1
  21296.  lseekdone:  ret
  21297.  mylseek     endp
  21298.  
  21299.  
  21300.  myread      proc   far C uses bx ds dx, handle:WORD, buf:PTR,len:WORD
  21301.              public myread
  21302.              mov    ah,3fH
  21303.              mov    bx,handle
  21304.              mov    cx,len
  21305.              lds    dx,buf
  21306.              int    21h             ;result nicely in ax for return
  21307.              jnc    readdone
  21308.              mov    ax,-1
  21309.  readdone:   ret
  21310.  myread      endp
  21311.  
  21312.  myopen      proc   far C uses ds dx, fname:PTR, mode:WORD
  21313.              public myopen
  21314.              mov    ax,mode
  21315.              mov    ah,3dH
  21316.              lds    dx,fname
  21317.              int    21h             ;result nicely in ax for return
  21318.              jnc    opendone
  21319.              mov    ax,-1
  21320.  opendone:   ret
  21321.  myopen      endp
  21322.  
  21323.  myclose     proc   far C uses bx, handle:WORD
  21324.              public myclose
  21325.              mov    ah,3eH
  21326.              mov    bx,handle
  21327.              int    21h             ;result nicely in ax for return
  21328.              ret
  21329.  myclose     endp
  21330.  
  21331.              end
  21332.  
  21333.  Figure 5
  21334.  
  21335.  OV.C
  21336.  
  21337.  #include <stdio.h>
  21338.  #include <string.h>
  21339.  
  21340.  void p1(void);
  21341.  void p2(void);
  21342.  
  21343.  extern char executable_name;
  21344.  
  21345.  void main(int argc, char **argv)
  21346.  {
  21347.      int i;
  21348.  
  21349.      strcpy(&executable_name,argv[0]);
  21350.      argc = argc;
  21351.  
  21352.      printf("This is main\n");
  21353.      for (i = 0; i < 1000; i++) {
  21354.          p1();
  21355.          p2();
  21356.      }
  21357.  }
  21358.  
  21359.  P1.C
  21360.  
  21361.  #include <stdio.h>
  21362.  void p1()
  21363.  {
  21364.     printf("In p1, whose address is %Fp\n",p1);
  21365.     printf("\t 3 * 3 + 9 = %d\n",3*3+9);
  21366.     printf("\t 4 * 4 / 5 = %d\n",4*4/5);
  21367.  }
  21368.  
  21369.  P2.C
  21370.  
  21371.  #include <stdio.h>
  21372.  void p2()
  21373.  {
  21374.     printf("In p2, whose address is %Fp\n",p2);
  21375.     printf("\t 10 %% 3 = %d\n",10 % 3);
  21376.  }
  21377.  
  21378.  Figure 6
  21379.  
  21380.  OVASM.ASM
  21381.  
  21382.              extrn  _errputs:far, p1:far, p2:far
  21383.              extrn  _executable_name:byte
  21384.  _text       segment public 'CODE'
  21385.  _text       ends
  21386.  _data       segment public 'DATA'
  21387.  _data       ends
  21388.  
  21389.              public fill_exe_name,ENVSEG,main_msg,
  21390.              public call_loop,envstr_loop
  21391.              public fname_loop
  21392.  
  21393.  ENVSEG      equ    2CH             ;loc'n of environment pointer
  21394.  DGROUP      group  _data,_stack
  21395.  _text       segment
  21396.              assume cs:_text,ds:DGROUP,ss:DGROUP
  21397.  _main       proc   far
  21398.              mov    ax,DGROUP
  21399.              mov    ds,ax
  21400.              call   far ptr fill_exe_name
  21401.              lea    ax,main_msg
  21402.              push   ds
  21403.              push   ax
  21404.              call   far ptr _errputs
  21405.              add    sp,4
  21406.              mov    cx,1000
  21407.  call_loop:
  21408.              call   far ptr p1
  21409.              call   far ptr p2
  21410.              loop   call_loop
  21411.              mov    ax,4c00h
  21412.              int    21h
  21413.  _main       endp
  21414.  
  21415.  fill_exe_name   proc   far
  21416.              push   es
  21417.              push   di
  21418.              push   ds
  21419.              push   si
  21420.              push   ax
  21421.  
  21422.              mov    ax,es
  21423.              mov    ds,ax
  21424.              mov    ax,DGROUP:ENVSEG  ;point at env
  21425.              mov    ds,ax
  21426.              mov    si,0
  21427.              mov    ax,seg _executable_name   ;point at dest string
  21428.              mov    es,ax
  21429.              lea    di,DGROUP:_executable_name
  21430.  envstr_loop:
  21431.              lodsb                      ;get first env
  21432.              cmp    al,0                ;end of string?
  21433.              jnz    envstr_loop         ;no, keep looking
  21434.              cmp    byte ptr ds:[si],0  ;end of env?
  21435.              jnz    envstr_loop         ;no, get another string
  21436.              add    si,3                ;yes: go past, 2 more
  21437.  fname_loop: movsb                      ;move a byte
  21438.              cmp    byte ptr ds:[si],0  ;end of name?
  21439.              jnz    fname_loop          ;no, keep it up
  21440.              mov    byte ptr es:[di],0  ;store zero terminator
  21441.  
  21442.              pop    ax
  21443.              pop    si
  21444.              pop    ds
  21445.              pop    di
  21446.              pop    es
  21447.              ret
  21448.  fill_exe_name   endp
  21449.  
  21450.  _text       ends
  21451.  
  21452.  
  21453.  _data       segment
  21454.  main_msg    db      'This is main...',13,10,0
  21455.  _data       ends
  21456.  
  21457.  
  21458.  _stack      segment stack 'STACK'
  21459.              dw     200 dup (?)
  21460.  _stack      ends
  21461.  
  21462.              end    _main
  21463.  
  21464.  P1ASM.ASM
  21465.  
  21466.              extrn  _errputs:far
  21467.              .model LARGE
  21468.              .data
  21469.  p1_msg      db     'This is p1...',13,10,0
  21470.  
  21471.              .code
  21472.  p1          proc   far
  21473.              public p1
  21474.              lea    ax,p1_msg
  21475.              push   ds
  21476.              push   ax
  21477.              call   _errputs
  21478.              add    sp,4
  21479.              ret
  21480.  p1          endp
  21481.  
  21482.              end
  21483.  
  21484.  P2ASM.ASM
  21485.  
  21486.              extrn   _errputs:far
  21487.              .model  LARGE
  21488.              .data
  21489.  p2_msg      db      'This is p2...',13,10,0
  21490.  
  21491.              .code
  21492.  p2          proc   far
  21493.              public p2
  21494.              lea    ax,p2_msg
  21495.              push   ds
  21496.              push   ax
  21497.              call   _errputs
  21498.              add    sp,4
  21499.              ret
  21500.  p2          endp
  21501.  
  21502.              end
  21503.  
  21504.  Figure 7
  21505.  
  21506.  OV
  21507.  
  21508.  ov.obj: ov.c
  21509.    cl /c /AL /Fm ov.c
  21510.  
  21511.  p1.obj: p1.c
  21512.    cl /c /AL /Fm p1.c
  21513.  
  21514.  p2.obj: p2.c
  21515.    cl /c /AL /Fm p2.c
  21516.  
  21517.  ovrmgr.obj: ovrmgr.asm
  21518.    masm /MX ovrmgr.asm;
  21519.  
  21520.  rdovly.asm : rdovly.c exehdr.h
  21521.  # /c compile only
  21522.  # /Fa create .ASM output
  21523.  # /Fonul don't create object (leave that for MASM)
  21524.  # /Al large model
  21525.  # /Gs no stack checking
  21526.  # /Zl no default library search
  21527.  # /Od no optimization (for code clarity)
  21528.    cl /c /Fa /Fonul /Od /AL /Gs /Zl rdovly.c
  21529.    smerge rdovly.asm rdovly.c rdovly.mrg
  21530.    del rdovly.asm
  21531.    ren rdovly.mrg rdovly.asm
  21532.  
  21533.  rdovly.obj : rdovly.asm
  21534.    masm /mx rdovly;
  21535.  
  21536.  support.obj : support.asm
  21537.    masm /mx support;
  21538.  
  21539.  # Note - the ov.exe : ... line cannot break but had to be broken
  21540.  #        here to fit on page
  21541.  
  21542.  ov.exe : ov.obj support.obj rdovly.obj ovrmgr.obj
  21543.           p1.obj p2.obj ovrmgr.obj
  21544.    link /m/li ov+support+rdovly+(p1)+(p2)+ovrmgr,ov,ov.map;
  21545.  
  21546.  OVASM
  21547.  
  21548.  ovasm.obj: ovasm.asm
  21549.    masm /mx ovasm.asm;
  21550.  
  21551.  p1asm.obj: p1asm.asm
  21552.    masm /mx p1asm;
  21553.  
  21554.  p2asm.obj: p2asm.asm
  21555.    masm /mx p2asm;
  21556.  
  21557.  ovrmgr.obj: ovrmgr.asm
  21558.    masm /MX ovrmgr.asm;
  21559.  
  21560.  rdovly.asm : rdovly.c exehdr.h
  21561.  # /c compile only
  21562.  # /Fa create .ASM output
  21563.  # /Fonul don't create object (leave that for MASM)
  21564.  # /Al large model
  21565.  # /Gs no stack checking
  21566.  # /Zl no default library search
  21567.  # /Od no optimization (for code clarity)
  21568.    cl /c /Fa /Fonul /Od /AL /Gs /Zl rdovly.c
  21569.    smerge rdovly.asm rdovly.c rdovly.mrg
  21570.    del rdovly.asm
  21571.    ren rdovly.mrg rdovly.asm
  21572.  
  21573.  rdovly.obj : rdovly.asm
  21574.    masm /mx rdovly;
  21575.  
  21576.  support.obj : support.asm
  21577.    masm /mx support;
  21578.  
  21579.  
  21580.  # Note - the ov.exe : ... and link lines cannot break but had
  21581.  #        to be broken here to fit on page
  21582.  
  21583.  ovasm.exe : ovasm.obj support.obj rdovly.obj ovrmgr.obj
  21584.              p1asm.obj p2asm.obj ovrmgr.obj
  21585.    link /m/li ovasm+support+rdovly+(p1asm)+(p2asm)+
  21586.               ovrmgr,ovasm,ovasm.map;
  21587.  
  21588.  Figure 8
  21589.  
  21590.  /* smerge - merge source and assembly files */
  21591.  
  21592.  #include <stdio.h>
  21593.  
  21594.  main(argc,argv)
  21595.  int argc;
  21596.  char *argv[];
  21597.  {
  21598.      FILE *csrc, *asmsrc, *mergefile;
  21599.      char buffer[128];
  21600.      long sline, line, totslines;
  21601.  
  21602.      if (argc < 4) {
  21603.          printf("usage: smerge asmsrc csrc mergefile\n");
  21604.          exit(1);
  21605.      }
  21606.  
  21607.      sline = 0;
  21608.  
  21609.      asmsrc = fopen(argv[1],"r");
  21610.      csrc = fopen(argv[2],"r");
  21611.      mergefile = fopen(argv[3],"w");
  21612.  
  21613.      setvbuf(csrc,NULL,_IOFBF,8192);
  21614.      setvbuf(asmsrc,NULL,_IOFBF,8192);
  21615.      setvbuf(mergefile,NULL,_IOFBF,8192);
  21616.  
  21617.      totslines = 0;
  21618.      while (fgets(buffer,128,csrc) != NULL)
  21619.          totslines++;
  21620.      fseek(csrc,0L,SEEK_SET);
  21621.  
  21622.      while ((fgets(buffer,128,asmsrc)) != NULL) {
  21623.  
  21624.          /* special hack for MSC -> assembly ...
  21625.             kill the "__acrtused" extrn */
  21626.  
  21627.          if (strstr(buffer,"__acrtused") != NULL)
  21628.              continue;
  21629.  
  21630.          if (strncmp(buffer,"; Line",6) == 0) {
  21631.              sscanf(buffer,"; Line %ld",&line);
  21632.              if (line <= totslines) {
  21633.                  while (sline < line) {
  21634.                      fgets(buffer+1,128,csrc);
  21635.                      fputs(buffer,mergefile);
  21636.                      ++sline;
  21637.                  }
  21638.              }
  21639.          } else
  21640.              fputs(buffer,mergefile);
  21641.      }
  21642.      fclose(asmsrc);
  21643.      fclose(csrc);
  21644.      fclose(mergefile);
  21645.  }
  21646.  
  21647.  
  21648.  
  21649.  Extended Memory Specification 2.x:
  21650.  
  21651.  Taking Advantage of the 80286 Protected Mode
  21652.  
  21653.   Chip Anderson
  21654.  
  21655.  The IBM PC and compatibles support three types of memory--conventional,
  21656.  expanded, and extended. Expanded memory has been widely supported by
  21657.  programmers for a number of years; extended memory, on the other hand, has
  21658.  been largely ignored. Now a new industry standard, known as the Extended
  21659.  Memory Specification (referred to herein as XMS), has made that memory more
  21660.  readily accessible to all programmers working in the DOS1 environment.
  21661.  This article takes a look at the evolution of extended memory and examines
  21662.  how XMS provides more memory for DOS applications.
  21663.  
  21664.  Extended Memory Issues
  21665.  
  21666.  DOS programmers have been slow to take advantage of extended memory for two
  21667.  primary reasons. The first is that until recently the number of machines
  21668.  that would not support extended memory (that is, 8088- and 8086-based
  21669.  machines) was far greater than the number that would. Most programmers,
  21670.  therefore, had to ignore the additional protected-mode capabilities of
  21671.  the 80286, since most copies of programs were purchased to run on the 8088
  21672.  or 8086 machines. But earlier in 1989, the sales of 286- and 386-based
  21673.  machines, many of which contained one or more megabytes of extended memory,
  21674.  began to surpass those of 8086- and 8088-based machines. Due to the
  21675.  popularity of the 286/386 machines, developers will certainly write programs
  21676.  to take advantage of extended memory.
  21677.  
  21678.  The second reason programmers were reluctant to provide access to extended
  21679.  memory was due to the lack of an accepted standard for accessing it. A
  21680.  standard for using extended memory must define methods that will allow
  21681.  programs to reserve part of the available extended memory for their
  21682.  exclusive use (allocation), copy data to and from extended memory
  21683.  (transfer), and release the reserved memory when finished (deallocation).
  21684.  
  21685.  Two conflicting pseudostandards for accessing extended memory have evolved
  21686.  over time. The IBM VDISK.SYS, a RAM disk program released with DOS Version
  21687.  3.0, allocates extended memory using the VDISK method. Programs such as
  21688.  Microsoft(R) RAMDRIVE.SYS use an alternate scheme known as the INT 15h
  21689.  method. Examining the details of how these two methods implement the
  21690.  allocation, transfer, and deallocation of extended memory demonstrates
  21691.  why using extended memory has remained difficult.
  21692.  
  21693.  Allocation
  21694.  
  21695.  VDISK.SYS was the first program to use extended memory and assumed that no
  21696.  other programs would use it. VDISK stores a data structure called a VDISK
  21697.  Header just above 1Mb. The VDISK Header indicates the amount of memory used
  21698.  by the first memory disk. If two VDISKs are installed, the second
  21699.  instance will install its header just above the end of the first disk. This
  21700.  process is called a bottom-up allocation scheme (see Figure 1).
  21701.  
  21702.  The INT 15h method approaches allocation from the opposite direction. When
  21703.  a DOS program uses INT 15h, Function 88h, the ROM BIOS returns the amount of
  21704.  extended memory in the computer. Using the INT 15h method, a DOS program
  21705.  reserves a portion of that memory by intercepting or hooking all
  21706.  subsequent INT 15h calls. After hooking INT 15h, the program gets control
  21707.  whenever another program executes INT 15h. This means that the first program
  21708.  can "lie" to all subsequent programs about the amount of extended memory
  21709.  that is installed. A consequence of this is that by returning a smaller
  21710.  amount, the first program guarantees that it has exclusive access to the
  21711.  remaining memory.
  21712.  
  21713.  A PC/AT(R) with 2Mb of RAM, for example, returns 1408Kb of memory when INT
  21714.  15h, Function 88h is executed (since 2048Kb - 640Kb = 1408Kb). When a 1Mb
  21715.  RAMDrive is installed, it hooks INT 15h so that all subsequent calls to
  21716.  INT 15h, Function 88h, return 384 (since 1408Kb - 1024Kb = 384Kb). If any
  21717.  other program uses INT 15h, Function 88h, it is fooled into thinking that
  21718.  the machine only contains 1Mb of RAM. RAMDrive is then free to store its
  21719.  data in the memory between 1408Kb and 2048Kb. If two RAMDrives are
  21720.  installed, the second instance also hooks INT 15h and has it tell other
  21721.  programs that even less memory is installed. Notice that the INT 15h
  21722.  method allocates memory in a top-down manner (see Figure 2).
  21723.  
  21724.  Since these methods allocate extended memory in different directions,
  21725.  they are incompatible. Another problem is that both methods assume that
  21726.  all available extended memory is contiguous, but several IBM compatibles
  21727.  have noncontiguous blocks of extended memory.
  21728.  
  21729.  Transfer
  21730.  
  21731.  Neither the VDISK method nor the INT 15h method specify a way in which to
  21732.  move data between conventional and extended memory. Therefore,
  21733.  programmers must write their own subroutine for doing this. But the only
  21734.  way of moving data into and out of extended memory is to quickly switch the
  21735.  microprocessor into 286 protected mode, perform the copy, and switch
  21736.  back into real mode. Realizing the difficulty in writing such a routine, IBM
  21737.  engineers provided a ROM BIOS function (INT 15h, AH=87h) to perform
  21738.  extended memory transfers in the original PC/AT computer.
  21739.  
  21740.  Unfortunately, the IBM ROM BIOS transfer subroutine has a major design
  21741.  problem--it disables hardware interrupts while data is being
  21742.  transferred. This means that during long transfers, time-dependent
  21743.  programs such as communications packages and networks will lose data,
  21744.  especially when they are running under multitasking environments such
  21745.  as Microsoft Windows/286 and Desqview. Programs that use the IBM
  21746.  transfer routine must break large transfers into many smaller chunks, thus
  21747.  significantly slowing an already slow process.
  21748.  
  21749.  Deallocation
  21750.  
  21751.  Unfortunately, there is no way to free allocated extended memory under
  21752.  either the VDISK or the INT 15h method of managing extended memory. To see
  21753.  why, consider the following scenario (see Figure 3). Running under a
  21754.  real-mode multitasking environment, for example Windows/286, Program A
  21755.  allocates 100Kb of extended memory, Program B allocates 50Kb of extended
  21756.  memory, and Program C allocates 75Kb of extended memory. How would each
  21757.  allocation scheme work if Program B finishes executing and wishes to
  21758.  free its 50Kb?
  21759.  
  21760.  Because the VDISK method is a bottom-up allocator, it assumes that no holes
  21761.  occur below the last block allocated. In this case, any future program that
  21762.  wishes to allocate extended memory has to find and use the header that
  21763.  Program C installed to determine how much extended memory is free and
  21764.  where it is located. The header does not indicate if any holes exist.
  21765.  
  21766.  The INT 15h method requires that the allocating program install an
  21767.  interrupt hook that fools other programs about the amount of available
  21768.  extended memory. Once they are installed, however, interrupt hooks cannot
  21769.  be removed without risk of a system crash. Not only does this fact prevent
  21770.  deallocation of extended memory, but, since interrupt hooks are part
  21771.  of a program, it means that the allocating program cannot be removed either.
  21772.  Therefore, only terminate-and-stay-resident (TSR) programs and device
  21773.  drivers can safely use the INT 15h method.
  21774.  
  21775.  High Memory
  21776.  
  21777.  Complicating the extended memory picture even further, developers at
  21778.  Microsoft discovered a simple hardware trick that allows DOS programs to
  21779.  access an additional 64Kb directly. In real mode, 286-based computers only
  21780.  enable 20 address lines and thereby emulate the 1Mb address space of
  21781.  8086-based computers. By selectively enabling and disabling the computer's
  21782.  21st address line, a DOS program running on a 286-based machine can access
  21783.  the first 64Kb of extended memory. This 64Kb area is called the High Memory
  21784.  Area (HMA). See "The High Memory Area: Addressing 64Kb More Memory in
  21785.  Real Mode," MSJ (Vol. 3, No. 6) for more information. Notice that the HMA
  21786.  must be located just above the 1Mb boundary and thus is incompatible with
  21787.  the VDISK allocation scheme.
  21788.  
  21789.  Introducing XMS
  21790.  
  21791.  In July 1988, after another period of lengthy discussions, Lotus(R),
  21792.  Intel(R), AST(R), and Microsoft (with input from many other vendors)
  21793.  finalized the Extended Memory Specification Version 2.0 (XMS 2.0). XMS 2.0
  21794.  solves all of the problems associated with both the VDISK and INT 15h
  21795.  methods of accessing extended memory. It provides a set of functions that
  21796.  DOS programs can use to do the following:
  21797.  
  21798.  ■    reserve portions of extended memory for themselves
  21799.  
  21800.  ■    transfer any amount of data from conventional memory into extended
  21801.  memory or vice versa
  21802.  
  21803.  ■    release reserved extended memory when finished
  21804.  
  21805.  In addition, XMS 2.0 provides functions that:
  21806.  
  21807.  ■    reserve and protect the HMA
  21808.  
  21809.  ■    control the computer's 21st address line in a machine-independent
  21810.  manner
  21811.  
  21812.  ■    reserve and release any additional memory located between 640Kb and
  21813.  1Mb (called Upper Memory Blocks)
  21814.  
  21815.  The XMS functions are typically added to the DOS system by installing an
  21816.  XMS driver via a "DEVICE=" command in the machine's CONFIG.SYS file. Anyone
  21817.  can get the Microsoft XMS driver, HIMEM.SYS, along with source code and the
  21818.  official XMS 2.0 specification by contacting the Microsoft Information
  21819.  Center at (800) 426-9400.
  21820.  
  21821.  Using XMS Functions
  21822.  
  21823.  Programs that wish to use XMS to access extended memory can do so by
  21824.  performing the following steps:
  21825.  
  21826.  1.    Determine if an XMS driver is installed.
  21827.  
  21828.  2.    Get the address of the driver's control function.
  21829.  
  21830.  3.    Set AH to the requested function's number.
  21831.  
  21832.  4.    Initialize other registers where appropriate.
  21833.  
  21834.  5.    Call the driver's control function.
  21835.  
  21836.  The first two steps are done via Interrupt 2Fh, the multiplex interrupt.
  21837.  Executing an INT 2Fh instruction with AX=4300h returns a value of 80h in
  21838.  AL if an XMS driver is installed. Programs can then get the address of the
  21839.  driver's control function by executing an INT 2Fh with AX=4310h. The driver
  21840.  will return the address in ES:BX. The program then has access to any of
  21841.  the 18 XMS functions (which are listed in the Extended Memory
  21842.  Specification sidebar). Some of these functions are defined in SCRXMS.ASM
  21843.  (Figure 4), a subset of the C library XMM.LIB that provides access to all
  21844.  XMS functions. XMM.LIB is included on the Microsoft XMS distribution
  21845.  diskette.
  21846.  
  21847.  Programs that need to store large amounts of data in extended memory only
  21848.  need to use the following XMS functions:
  21849.  
  21850.  08h    Query Free Extended Memory
  21851.  
  21852.  09h    Allocate Extended Memory Block
  21853.  
  21854.  0Ah    Free Extended Memory Block
  21855.  
  21856.  0Bh    Move Extended Memory Block
  21857.  
  21858.  These functions are demonstrated by the SCRSAVE program (see Figure 5),
  21859.  which shows how a DOS program can use XMS calls to store data in extended
  21860.  memory.
  21861.  
  21862.  Query Free Extended Memory (08h) returns two numbers--the total
  21863.  amount of free extended memory and the size of the largest contiguous free
  21864.  Extended Memory Block (EMB). Because most programs only work with contiguous
  21865.  data buffers, the second number is usually more important than the first
  21866.  number.
  21867.  
  21868.  Allocate Extended Memory Block (09h) attempts to allocate a contiguous EMB
  21869.  that is at least as big as the caller requested, in this case 8Kb of
  21870.  extended memory for the screen:
  21871.  
  21872.  lret = XMM_AllocateExtended(8);
  21873.  
  21874.  (See also _XMM_AllocateExtended in Figure 4.)
  21875.  
  21876.  If the allocation succeeds, a 16-bit EMB Handle is returned to the caller:
  21877.  
  21878.  xHandle = (WORD)lret;
  21879.  
  21880.  If there is less than 8Kb available, then an appropriate error message
  21881.  is generated and the memory is not allocated:
  21882.  
  21883.  if (!xHandle)
  21884.  {
  21885.    printf("Allocation Error: %X/n",
  21886.           XMSERROR(lret));
  21887.    exit(-1);
  21888.  }
  21889.  
  21890.  The caller can use this handle in subsequent XMS calls as a nickname for the
  21891.  EMB. 0000h is reserved as a special type of EMB handle that refers to
  21892.  memory below the 1Mb boundary.
  21893.  
  21894.  Move Extended Memory Block (0Bh) can be used to move data from any part of
  21895.  memory to any other part. Typically, it is used to move data from
  21896.  conventional memory into extended memory or vice versa. The caller passes
  21897.  the function a pointer to a data structure, which contains pointers for the
  21898.  source and destination buffers along with the number of data words to
  21899.  transfer. In this case, SCRSAVE stores the contents of the test screen in an
  21900.  EMB. It then fills the screen with a test pattern:
  21901.  
  21902.  lret = XMM_MoveExtended
  21903.         (&MoveStruct);
  21904.  
  21905.  The function performs the transfer without disabling interrupts, thus side
  21906.  stepping the problems with INT 15h, Function 87h. In addition, HIMEM.SYS
  21907.  will use a faster version of the Move function when it is installed on an
  21908.  80386-based machine, thus ensuring the fastest transfer rate regardless of
  21909.  the hardware platform.
  21910.  
  21911.  Free Extended Memory Block (0Ah) deallocates the previously allocated EMB.
  21912.  SCRSAVE retrieves the old contents of the screen from extended memory,
  21913.  restores the screen, and terminates:
  21914.  
  21915.  XMM_FreeExtended(xHandle);
  21916.  
  21917.  Since XMS does not use hooks or headers, deallocation is always possible. As
  21918.  with any reasonable memory management scheme, after each deallocation
  21919.  any adjacent blocks that are free are merged into one larger free block.
  21920.  
  21921.  The standardization of XMS and the development of the XMM function library
  21922.  have made extended memory easily accessible to DOS programmers. This
  21923.  means that you will begin to see more applications designed specifically to
  21924.  take advantage of the 286/386 extended memory capabilities. This article
  21925.  describes the extended memory functions. These functions will help give
  21926.  your programs instant access to more memory.
  21927.  
  21928.  
  21929.  
  21930.  Figure 4
  21931.  
  21932.  ; SCRXMS.ASM - Adapted from the XMM C Library Routines (c) 1988 ;
  21933.  ;Microsoft Corp
  21934.  
  21935.  ; DATA SEGMENT
  21936.  
  21937.  _DATA    segment WORD PUBLIC 'DATA'
  21938.  
  21939.  XMM_Initialized dw    0        ; 1 if the XMS driver has
  21940.                                                      ; been found
  21941.  XMM_Control    label    dword        ; Points to the XMS control function
  21942.          dw    offset _XMM_NotInitialized
  21943.          dw    seg _TEXT
  21944.  _DATA    ends
  21945.  
  21946.  
  21947.  DGROUP    GROUP _DATA
  21948.  
  21949.  ; CODE SEGMENT
  21950.  
  21951.  _TEXT    segment WORD PUBLIC 'CODE'
  21952.  
  21953.  assume cs:_TEXT
  21954.  assume ds:DGROUP
  21955.  
  21956.  ;-------------------------------------------------------------------
  21957.  ;  _XMM_NotInitialized
  21958.  ;    Called by default if the program hasn't called _XMM_Installed
  21959.  ;    or if no XMS driver is found.
  21960.  ;-------------------------------------------------------------------
  21961.  
  21962.  _XMM_NotInitialized proc far
  21963.  
  21964.      xor    ax,ax            ; Simulate an XMS "Not
  21965.                      ; Implemented" error
  21966.      mov    bl,80h
  21967.      ret
  21968.  
  21969.  _XMM_NotInitialized endp
  21970.  
  21971.  ;-------------------------------------------------------------------
  21972.  ;  BOOL _XMM_Installed(void);
  21973.  ;    Called to initialize the XMM library routines.
  21974.  ;-------------------------------------------------------------------
  21975.  
  21976.  public _XMM_Installed
  21977.  
  21978.  _XMM_Installed proc near
  21979.  
  21980.      ; Have we already been initialized?
  21981.      cmp    [XMM_Initialized],0
  21982.      jne    Already_Initialized        ; Yes, return TRUE
  21983.  
  21984.      ; Is an XMS driver installed?
  21985.      mov    ax,4300h
  21986.      int    2Fh
  21987.      cmp    al,80h
  21988.      jne    NoDriver            ; No, return FALSE
  21989.  
  21990.      ; Get and store the address of the driver's control function.
  21991.      mov    ax,4310h
  21992.      int    2Fh
  21993.      mov    word ptr [XMM_Control],bx
  21994.      mov    word ptr [XMM_Control+2],es
  21995.  
  21996.      ; Set XMM_Initialized to TRUE.
  21997.      inc    [XMM_Initialized]
  21998.  
  21999.  Already_Initialized:
  22000.  NoDriver:
  22001.      mov    ax,[XMM_Initialized]
  22002.      ret
  22003.  
  22004.  _XMM_Installed endp
  22005.  
  22006.  ;-------------------------------------------------------------------
  22007.  ;  WORD _XMM_Version(void);
  22008.  ;    Returns the driver's XMS version number.
  22009.  ;-------------------------------------------------------------------
  22010.  public _XMM_Version
  22011.  
  22012.  _XMM_Version proc near
  22013.  
  22014.      xor    ah,ah            ; Call XMS Function 0
  22015.      call    [XMM_Control]        ; Sets AX to XMS Version
  22016.      ret
  22017.  
  22018.  _XMM_Version endp
  22019.  
  22020.  ;-------------------------------------------------------------------
  22021.  ;  long _XMM_AllocateExtended(int SizeK);
  22022.  ;    Allocates an Extended Memory Block and returns its handle.
  22023.  ;-------------------------------------------------------------------
  22024.  public _XMM_AllocateExtended
  22025.  
  22026.  _XMM_AllocateExtended proc near
  22027.  
  22028.      push    bp            ; Create a stack frame
  22029.      mov    bp,sp
  22030.  
  22031.      ; Call the XMS Allocate function.
  22032.      mov    ah,9
  22033.      mov    dx,[bp+4]        ; [bp+4] is the parameter
  22034.      call    [XMM_Control]
  22035.  
  22036.      ; Was there an error?
  22037.      or    ax,ax
  22038.      jz    AEFail            ; Yup, fail
  22039.      mov    ax,dx            ; Return XMS Handle in AX
  22040.      mov    dx,0            ; Zero out DX
  22041.      jnz    AESuccess
  22042.  AEFail:
  22043.      mov    dh,bl            ; Put error return in DH
  22044.  
  22045.  AESuccess:
  22046.      pop    bp            ; Restore the stack
  22047.      ret
  22048.  
  22049.  _XMM_AllocateExtended endp
  22050.  
  22051.  ;-------------------------------------------------------------------
  22052.  ;  long _XMM_FreeExtended(WORD Handle);
  22053.  ;    Deallocates an Extended Memory Block.
  22054.  ;-------------------------------------------------------------------
  22055.  public _XMM_FreeExtended
  22056.  
  22057.  _XMM_FreeExtended proc near
  22058.  
  22059.      push    bp            ; Create a stack
  22060.                      ; frame
  22061.      mov    bp,sp
  22062.  
  22063.      ; Call the XMS FreeExtended function.
  22064.      mov    ah,0Ah
  22065.      mov    dx,[bp+4]        ; [bp+4] is the parameter
  22066.      call    [XMM_Control]
  22067.  
  22068.      xor    dx,dx            ; Zero DX
  22069.  
  22070.      ; Was there an error?
  22071.      or    ax,ax
  22072.      jz    FESuccess        ; Nope, return
  22073.      mov    dh,bl            ; Yup, return err code in DH
  22074.  FESuccess:
  22075.      pop    bp            ; Restore the stack
  22076.      ret
  22077.  
  22078.  _XMM_FreeExtended endp
  22079.  
  22080.  ;-------------------------------------------------------------------
  22081.  ;  long _XMM_MoveExtended(XMSMOVE *pInfo);
  22082.  ;    Moves a block of data into/outof/within extended memory.
  22083.  ;-------------------------------------------------------------------
  22084.  public _XMM_MoveExtended
  22085.  
  22086.  _XMM_MoveExtended proc near
  22087.  
  22088.      push    bp            ; Create a stack frame
  22089.      mov    bp,sp
  22090.  
  22091.      ; Call the XMS MoveExtended function.
  22092.      mov    ah,0Bh
  22093.      mov    si,[bp+4]        ; [bp+4] is the parameter
  22094.      call    [XMM_Control]
  22095.  
  22096.      xor    dx,dx            ; Zero DX
  22097.  
  22098.      ; Was there an error?
  22099.      or    ax,ax
  22100.      jz    MESuccess        ; Nope, return
  22101.      mov    dh,bl            ; Yup, return err code in DH
  22102.  MESuccess:
  22103.      pop    bp            ; Restore the stack
  22104.      ret
  22105.  
  22106.  _XMM_MoveExtended endp
  22107.  
  22108.  
  22109.  _TEXT    ends
  22110.  
  22111.  end
  22112.  
  22113.  Figure 5
  22114.  
  22115.  SCRSAVE
  22116.  
  22117.  scrxms.obj: scrxms.asm
  22118.      masm scrxms;
  22119.  
  22120.  scrsave.obj: scrsave.c
  22121.      cl -c scrsave.c
  22122.  
  22123.  scrsave.exe: scrxms.obj scrsave.obj
  22124.      link scrsave+scrxms,,,slibcec;
  22125.  
  22126.  SCRSAVE.C
  22127.  
  22128.  /********************************************************************
  22129.  SCRSAVE.C -     Chip Anderson
  22130.  XMS Demonstration Program.  Saves the contents of the text screen in
  22131.  extended memory, writes a test pattern on the screen, then restores the
  22132.  screen's original contents.  11/1/88
  22133.  ********************************************************************/
  22134.  
  22135.  #include "stdio.h"
  22136.  
  22137.  #define WORD unsigned int
  22138.  
  22139.  WORD XMM_Installed(void);
  22140.  WORD XMM_Version(void);
  22141.  long XMM_AllocateExtended(WORD);
  22142.  long XMM_FreeExtended(WORD);
  22143.  long XMM_MoveExtended(struct XMM_Move *);
  22144.  
  22145.  struct XMM_Move
  22146.    {
  22147.      unsigned long   Length;
  22148.      unsigned int    SourceHandle;
  22149.      unsigned long   SourceOffset;
  22150.      unsigned int    DestHandle;
  22151.      unsigned long   DestOffset;
  22152.    };
  22153.  
  22154.  #define XMSERROR(x)    (unsigned char)((x)>>24)
  22155.  
  22156.  main()
  22157.  
  22158.  {
  22159.    int        i,j;
  22160.    long        lret;
  22161.    WORD        xHandle = 0;
  22162.    struct XMM_Move   MoveStruct;
  22163.  
  22164.    /* Display the credits. */
  22165.    printf("SCRSAVE - XMS Demonstration Program\n");
  22166.  
  22167.    /* Is an XMS Driver installed? */
  22168.    if (!XMM_Installed())
  22169.      {
  22170.        printf("No XMS Driver was found.\n");
  22171.        exit(-1);
  22172.      }
  22173.  
  22174.    /* Is it a version 2.00 driver (or higher)? */
  22175.    if (XMM_Version() < 0x200)
  22176.      {
  22177.        printf("This program requires an XMS driver version 2.00 or \
  22178.  higher.\n");
  22179.        exit(-1);
  22180.      }
  22181.  
  22182.    /* We've found a legal XMS driver.  Now allocate 8K of extended
  22183.       memory for the screen. */
  22184.  
  22185.    lret = XMM_AllocateExtended(8);
  22186.    xHandle = (WORD)lret;
  22187.  
  22188.    if (!xHandle)
  22189.      {
  22190.        printf("Allocation Error: %X\n", XMSERROR(lret));
  22191.        exit(-1);
  22192.      }
  22193.  
  22194.    /* Copy the contents of the screen video buffer into the extended
  22195.       memory block we just allocated. */
  22196.  
  22197.    MoveStruct.SourceHandle = 0;    /* Source is in conventional
  22198.                                     memory */
  22199.    MoveStruct.SourceOffset = 0xB8000000L;/*Text screen data is a
  22200.                                            B800:0000 */
  22201.    MoveStruct.DestHandle   = xHandle;
  22202.    MoveStruct.DestOffset   = 0L;
  22203.    MoveStruct.Length    = 8000L;    /* 80 x 25 x 4 */
  22204.  
  22205.    lret = XMM_MoveExtended(&MoveStruct);
  22206.  
  22207.    if (!(WORD)lret)
  22208.      {
  22209.        printf("Memory Move Error: %X\n", XMSERROR(lret));
  22210.        exit(-1);
  22211.      }
  22212.  
  22213.    /* Fill the screen with garbage. */
  22214.    for (i=0; i < 22; i++)
  22215.        for (j=64; j < (64+81); j++)
  22216.      printf("%c",j);
  22217.  
  22218.    /* Prompt the user. */
  22219.    printf("Press Enter to restore the screen:");
  22220.    getchar();
  22221.  
  22222.    /* Restore the screen buffer. */
  22223.    MoveStruct.DestHandle   = 0;
  22224.    MoveStruct.DestOffset   = 0xB8000000L;
  22225.    MoveStruct.SourceHandle = xHandle;
  22226.    MoveStruct.SourceOffset = 0L;
  22227.    MoveStruct.Length    = 8000L;
  22228.  
  22229.    lret = XMM_MoveExtended(&MoveStruct);
  22230.  
  22231.    if (!(WORD)lret)
  22232.      {
  22233.        printf("Memory Move Error: %X\n", XMSERROR(lret));
  22234.        exit(-1);
  22235.      }
  22236.  
  22237.    /* Free the extended memory buffer. */
  22238.    XMM_FreeExtended(xHandle);
  22239.  
  22240.    printf("Program terminated normally.");
  22241.    exit(0);
  22242.  }
  22243.  
  22244.  
  22245.  
  22246.  Extended Memory Specification
  22247.  
  22248.  Get XMS Version Number (Function 00h)
  22249.  
  22250.  ARGS:    AH = 00h
  22251.      RETS:    AX = XMS version number
  22252.          BX = Driver internal revision number
  22253.          DX = 0001h if the HMA exists, 0000h otherwise
  22254.  
  22255.  This function returns with AX equal to a 16-bit BCD number representing the
  22256.  revision of the DOS Extended Memory Specification (XMS) that the driver
  22257.  implements (for example, AX=0235h would mean that the driver implemented XMS
  22258.  Version 2.35).
  22259.  
  22260.  Request High Memory Area (Function 01h)
  22261.  
  22262.  ARGS:    AH = 01h
  22263.          If the caller is a TSR or device driver,
  22264.          DX = Space needed in the HMA by the caller
  22265.           in bytes
  22266.          If the caller is an application program,
  22267.          DX = FFFFh
  22268.      RETS:   AX = 0001h if the HMA is assigned to the caller,
  22269.           0000h otherwise
  22270.  
  22271.  This function attempts to reserve the 64Kb-by-16 byte high memory area (HMA)
  22272.  for the caller. If the HMA is currently unused, the caller's size parameter
  22273.  is compared to the /HMAMIN= parameter on the driver's command line. If the
  22274.  value passed by the caller is greater than or equal to the amount specified
  22275.  by the driver's parameter, the request succeeds. This provides the ability
  22276.  to ensure that programs that use the HMA efficiently have priority over
  22277.  those that do not.
  22278.  
  22279.  Release High Memory Area (Function 02h)
  22280.  
  22281.  ARGS:    AH = 02h
  22282.      RETS:     AX = 0001h if the HMA is successfully released,
  22283.  0000h otherwise
  22284.  
  22285.  This function releases the HMA and allows other programs to use it. Programs
  22286.  that allocate the HMA must release it before exiting. When the HMA has been
  22287.  released, any code or data stored in it becomes invalid and should not be
  22288.  accessed.
  22289.  
  22290.  Global Enable A20 (Function 03h)
  22291.  
  22292.  ARGS:    AH = 03h
  22293.      RETS:    AX = 0001h if the A20 line is enabled,
  22294.           0000h  otherwise
  22295.  
  22296.  This function attempts to enable the A20 line. It should only be used by
  22297.  programs that have control of the HMA. The A20 line should be turned off via
  22298.  Function 04h (Global Disable A20) before a program releases control of the
  22299.  system. (Note: On many machines, toggling the A20 line is a relatively slow
  22300.  operation.)
  22301.  
  22302.  Global Disable A20 (Function 04h)
  22303.  
  22304.  ARGS:    AH = 04h
  22305.      RETS:     AX = 0001h if the A20 line is disabled,
  22306.          0000h otherwise
  22307.  
  22308.  This function attempts to disable the A20 line. It should only be used by
  22309.  programs that have control of the HMA. The A20 line should be disabled
  22310.  before a program releases control of the system.
  22311.  
  22312.  Local Enable A20 (Function 05h)
  22313.  
  22314.  ARGS:    AH = 05h
  22315.      RETS:    AX = 0001h if the A20 line is enabled,
  22316.          0000h otherwise
  22317.  
  22318.  This function attempts to enable the A20 line. It should only be used by
  22319.  programs that need direct access to extended memory. Programs that use this
  22320.  function should call Function 06h (Local Disable A20) before releasing
  22321.  control of the system.
  22322.  
  22323.  Local Disable A20 (Function 06h)
  22324.  
  22325.  ARGS:    AH = 06h
  22326.      RETS:    AX = 0001h if the function succeeds,
  22327.          0000h otherwise
  22328.  
  22329.  This function cancels a previous call to Function 05h (Local Enable A20). It
  22330.  should only be used by programs that need direct access to extended memory.
  22331.  Previous calls to Function 05h must be canceled before releasing control of
  22332.  the system.
  22333.  
  22334.  Query A20 (Function 07h)
  22335.  
  22336.  ARGS:    AH = 07h
  22337.      RETS:    AX = 0001h if the A20 line is physically enabled,
  22338.           0000h otherwise
  22339.  
  22340.  This function checks to see if the A20 line is physically enabled. It does
  22341.  this in a hardware-independent manner by seeing if memory wrap occurs.
  22342.  
  22343.  Query Free Extended Memory (Function 08h)
  22344.  
  22345.  ARGS:     AH = 08h
  22346.      RETS:     AX = Size of the largest free extended memory
  22347.           block in kilobytes
  22348.          DX = Total amount of free extended memory
  22349.          in  kilobytes
  22350.  
  22351.  This function returns the size of the largest available extended memory
  22352.  block (EMB) in the system. (Note: The 64Kb HMA is not included in the
  22353.  returned value even if it is not in use.)
  22354.  
  22355.  Allocate Extended Memory Block (Function 09h)
  22356.  
  22357.  ARGS:     AH = 09h
  22358.          DX = Amount of extended memory being
  22359.          requested in kilobytes
  22360.      RETS:     AX = 0001h if the block is allocated,
  22361.          0000h otherwise
  22362.          DX = 16-bit handle to the allocated block
  22363.  
  22364.  This function attempts to allocate a block of the given size out of the pool
  22365.  of free extended memory. If a block is available, it is reserved for the
  22366.  caller, and a 16-bit handle to that block is returned. The handle should be
  22367.  used in all subsequent extended memory calls. If no memory was allocated,
  22368.  the returned handle is null. (Note: Extended memory handles are scarce
  22369.  resources. Programs should try to allocate as few as possible at any one
  22370.  time. When all of a driver's handles are in use, any free extended memory is
  22371.  unavailable.)
  22372.  
  22373.  Free Extended Memory Block (Function 0Ah)
  22374.  
  22375.  ARGS:    AH = 0Ah
  22376.          DX = Handle to the allocated block that should
  22377.          be freed
  22378.      RETS:    AX = 0001h if the block is successfully freed,
  22379.          0000h otherwise
  22380.  
  22381.  This function frees a block of extended memory that was previously allocated
  22382.  using Function 09h (Allocate Extended Memory Block). Programs that allocate
  22383.  extended memory should free their memory blocks before exiting. When an
  22384.  extended memory buffer is freed, its handle and all data stored in it become
  22385.  invalid and should not be accessed.
  22386.  
  22387.  Move Extended Memory Block (Function 0Bh)
  22388.  
  22389.  ARGS:    AH = 0Bh
  22390.          DS:SI = Pointer to an Extended Memory Move
  22391.          Structure  (see below)
  22392.      RETS:    AX = 0001h if the move is successful,
  22393.          0000h otherwise
  22394.  
  22395.  Extended Memory Move Structure Definition
  22396.  
  22397.  ExtMemMoveStruct    struct
  22398.              Length    dd  ?    ; 32-bit number of bytes
  22399.                  ; to  transfer
  22400.              SourceHandle    dw  ?    ; Handle of source
  22401.                  ; block
  22402.              SourceOffset    dd  ?    ; 32-bit offset into
  22403.                  ; source
  22404.              DestHandle    dw  ?    ; Handle of
  22405.                  ; destination block
  22406.              DestOffset    dd  ?    ; 32-bit offset into
  22407.                  ; destination block
  22408.          ExtMemMoveStruct    ends
  22409.  
  22410.  
  22411.  
  22412.  This function attempts to transfer a block of data from one location to
  22413.  another. It is primarily intended for moving blocks of data between
  22414.  conventional memory and extended memory; however, it can be used for moving
  22415.  blocks within conventional memory and within extended memory.
  22416.  
  22417.  Length must be even. Although not required, WORD-aligned moves can be
  22418.  significantly faster on most machines. DWORD aligned moves can be even
  22419.  faster on 80386 machines.
  22420.  
  22421.  SourceHandle and DestHandle do not have to refer to locked memory blocks.
  22422.  
  22423.  (Note: If SourceHandle is set to 0000h, the SourceOffset is interpreted as a
  22424.  standard segment:offset pair that refers to memory that is directly
  22425.  accessible by the processor. The segment:offset pair is stored in Intel
  22426.  DWORD notation. The same is true for DestHandle and DestOffset.)
  22427.  
  22428.  If the source and destination blocks overlap, only forward moves (that is,
  22429.  those in which the source base is less than the destination base) are
  22430.  guaranteed to work properly.
  22431.  
  22432.  Programs should not enable the A20 line before calling this function. The
  22433.  state of the A20 line is preserved. This function is guaranteed to provide a
  22434.  reasonable number of interrupt windows during long transfers.
  22435.  
  22436.  Lock Extended Memory Block (Function 0Ch)
  22437.  
  22438.  ARGS:     AH = 0Ch
  22439.          DX = Extended memory block handle to lock
  22440.      RETS:     AX = 0001h if the block is locked,
  22441.          0000h otherwise
  22442.          DX:BX = 32-bit linear address of the
  22443.          locked block
  22444.  
  22445.  
  22446.  
  22447.  This function locks an EMB and returns its base address as a 32-bit linear
  22448.  address. Locked memory blocks are guaranteed not to move. The 32-bit pointer
  22449.  is only valid while the block is locked. Locked blocks should be unlocked as
  22450.  soon as possible.
  22451.  
  22452.  Unlock Extended Memory Block (Function 0Dh)
  22453.  
  22454.  ARGS:     AH = 0Dh
  22455.          DX = Extended memory block handle to unlock
  22456.      RETS:    AX = 0001h if the block is unlocked,
  22457.          0000h otherwise
  22458.  
  22459.  
  22460.  
  22461.  This function unlocks a locked EMB. Any 32-bit pointers into the block
  22462.  become invalid and should no longer be used.
  22463.  
  22464.  Get EMB Handle Information (Function 0Eh)
  22465.  
  22466.  ARGS:    AH = 0Eh
  22467.          DX = Extended memory block handle
  22468.      RETS:    AX = 0001h if the block's information is found,
  22469.           0000h otherwise
  22470.          BH = The block's lock count
  22471.          BL = Number of free EMB handles in the system
  22472.          DX = The block's length in kilobytes
  22473.  
  22474.  This function returns additional information about an EMB to the caller.
  22475.  [Note: To get the block's base address, use Function 0Ch (Lock Extended
  22476.  Memory Block).]
  22477.  
  22478.  
  22479.  
  22480.  Reallocate Extended Memory Block (Function 0Fh)
  22481.  
  22482.  ARGS:     AH = 0Fh
  22483.          BX = New size for the extended memory block
  22484.          in  kilobytes
  22485.          DX = Unlocked extended memory block handle
  22486.          to  reallocate
  22487.      RETS:    AX = 0001h if the block is reallocated,
  22488.          0000h otherwise
  22489.  
  22490.  This function attempts to reallocate an unlocked EMB so that it becomes the
  22491.  newly specified size. If the new block's size is smaller than the old one's,
  22492.  all data at the upper end of the old block is lost.
  22493.  
  22494.  Request Upper Memory Block (Function 10h)
  22495.  
  22496.  ARGS:    AH = 10h
  22497.          DX = Size of requested memory block in
  22498.          paragraphs
  22499.      RETS:    AX = 0001h if the request is granted,
  22500.          0000h otherwise
  22501.          BX = Segment number of the upper
  22502.          memory block
  22503.          If the request is granted,
  22504.          DX = Actual size of the allocated block in
  22505.          paragraphs otherwise,
  22506.          DX = Size of the largest available upper memory
  22507.          block in paragraphs
  22508.  
  22509.  This function attempts to allocate an upper memory block (UMB) to the
  22510.  caller. If the function fails, the size of the largest free UMB is returned
  22511.  in DX.
  22512.  
  22513.  Release Upper Memory Block (Function 11h)
  22514.  
  22515.  ARGS:     AH = 11h
  22516.          DX = Segment number of the upper
  22517.          memory block
  22518.      RETS:    AX = 0001h if the block was released,
  22519.          0000h otherwise
  22520.  
  22521.  This function frees a previously allocated UMB. When an UMB has been
  22522.  released, any code and data stored in it becomes invalid and should not be
  22523.  accessed.
  22524.  
  22525.  ERROR CODE INDEX
  22526.  
  22527.  If AX=0000h when a function returns and the high bit of         BL is set,
  22528.  BL equals
  22529.  
  22530.          80h if the function is not implemented
  22531.         81h if a VDISK device is detected
  22532.         82h if an A20 error occurs
  22533.         8Eh if a general driver error occurs
  22534.         8Fh if an unrecoverable driver error occurs
  22535.         90h if the HMA does not exist
  22536.         91h if the HMA is already in use
  22537.         92h if DX is less than the /HMAMIN= parameter
  22538.         93h if the HMA is not allocated
  22539.         94h if the A20 line is still enabled
  22540.         A0h if all extended memory is allocated
  22541.         A1h if all available extended memory handles are
  22542.             in  use
  22543.         A2h if the handle is invalid
  22544.         A3h if the SourceHandle is invalid
  22545.         A4h if the SourceOffset is invalid
  22546.         A5h if the DestHandle is invalid
  22547.         A6h if the DestOffset is invalid
  22548.         A7h if the Length is invalid
  22549.         A8h if the move has an invalid overlap
  22550.         A9h if a parity error occurs
  22551.         AAh if the block is not locked
  22552.         ABh if the block is locked
  22553.         ACh if the block's lock count overflows
  22554.         ADh if the lock fails
  22555.         B0h if a smaller UMB is available
  22556.         B1h if no UMBs are available
  22557.         B2h if the UMB segment number is invalid
  22558.  
  22559.  Copyright (c) 1988, Microsoft Corporation
  22560.  
  22561.  
  22562.  
  22563.  Exploring the Key Functions of the OS/2 Keyboard and Mouse Subsystems
  22564.  
  22565.   Richard Hale Shaw
  22566.  
  22567.  The OS/2 operating systems (referred to herein as OS/2) offer services for
  22568.  handling user input from both the keyboard and the mouse. This article will
  22569.  explore the KBD and MOU subsystems, each of which contains a core set of
  22570.  functions that you will be able to use regularly in most applications. We'll
  22571.  also glance at some of the lesser-used, more esoteric functions. Finally,
  22572.  we'll include a simple program that builds on the knowledge of threads and
  22573.  the VIO subsystem that you've obtained from previous articles in this
  22574.  series. It will show you how to incorporate OS/2 keyboard and mouse
  22575.  functions quickly and easily into your own multithreaded applications.
  22576.  
  22577.  KBD Subsystem
  22578.  
  22579.  Since most PC programs are highly interactive and rely heavily on effective
  22580.  interpretation of and efficient interaction with the keyboard, an
  22581.  application's ability to deal effectively with the keyboard is second only
  22582.  to its ability to handle its video output. OS/2 keyboard services therefore
  22583.  provide applications with basic keyboard input facilities plus a variety of
  22584.  additional keyboard functions.
  22585.  
  22586.  In theory, an application can use the DosOpen Application Programming
  22587.  Interface (API) routine to open its session's STDIN handle and receive
  22588.  character-based data from the keyboard. In practice, however, it makes more
  22589.  sense to use OS/2 keyboard services, particularly in highly interactive
  22590.  programs. Or, if you wish, you can bypass the keyboard subsystem entirely
  22591.  and go directly to the keyboard device driver via IOCTL services.
  22592.  
  22593.  The keyboard device driver, KBD$, provides the physical keyboard support for
  22594.  OS/2, which loads it from KBD01.SYS or KBD02.SYS. This device driver
  22595.  services the keyboard interrupts and translates keyboard scan codes into
  22596.  characters via the code page in use by the current screen group. KBD$ also
  22597.  works with  the OS/2 kernel to create and maintain the device monitor chains
  22598.  for each screen group. The primary concern of the keyboard subsystem,
  22599.  however, is an application's ability to receive and process keystrokes.
  22600.  
  22601.  With OS/2 keyboard subsystem services, a program can obtain a key's ASCII
  22602.  code and scan code, the keyboard shift state, and a time stamp every time
  22603.  the user presses a key. The keyboard state affects all keyboard input
  22604.  functions and includes the status of the shift and toggle keys and the
  22605.  turnaround (end-of-input) character. It also affects whether characters are
  22606.  echoed and the character input mode (discussed below). The keyboard state
  22607.  can be retrieved and altered for the session in which the program is
  22608.  running.
  22609.  
  22610.  More advanced keyboard services provide code-page switching, keyboard
  22611.  monitoring, and a means of replacing the keyboard subsystem services with
  22612.  functions of your own. Code-page switching allows an application to switch
  22613.  the character set used for keyboard interpretation. Keyboard monitor
  22614.  services allow background applications to monitor keyboard inputs for other
  22615.  processes in the same screen group. Keyboard replacement services allow an
  22616.  application to replace many of the keyboard service routines with its own.
  22617.  
  22618.  Like its video services, OS/2 supplies the keyboard subsystem as
  22619.  dynamic-link libraries (DLLs) and several API services. Two DLLs support the
  22620.  keyboard API: KBDCALLS.DLL and BKSCALLS.DLL. KBDCALLS.DLL is known as the
  22621.  keyboard router. It contains the entry points for the KBD API and passes the
  22622.  calls to the subsystem in use in each screen group. This is usually
  22623.  BKSCALLS.DLL (the default subsystem). BKSCALLS.DLL contains the Basic
  22624.  Keyboard Subsystem and implements the KBD services. All or part of the
  22625.  Keyboard Subsystem may be replaced in each screen group.
  22626.  
  22627.  In "Using the OS/2 Video I/O Subsystem to Create Appealing Visual
  22628.  Interfaces," MSJ (Vol. 4, No. 3), you saw that the OS/2 video services are a
  22629.  superset of the IBM(R) PC ROM BIOS Int 10h function. Similarly, some OS/2
  22630.  keyboard functions resemble ROM BIOS Int 16h. Each of the 16 keyboard
  22631.  services are prefixed with Kbd to designate them as part of the KBD
  22632.  subsystem. Even though many of them overlap some C standard library
  22633.  routines, they are more powerful. You can continue to use C library routines
  22634.  for generic keyboard input, but you will need to use KBD calls to gain full
  22635.  control of the keyboard.
  22636.  
  22637.  Logical Keyboards
  22638.  
  22639.  As you have seen in this series of articles, OS/2 "virtualizes" the
  22640.  resources of the computer. The OS/2 Session Manager executes every OS/2
  22641.  application in its own screen group. A program started in the OS/2
  22642.  Presentation Manager (referred to herein as PM) screen group will either
  22643.  execute in a PM window or in its own session. For VIO, this meant that each
  22644.  program or process can access a logical video buffer, even when running in a
  22645.  PM window.
  22646.  
  22647.  The same is true for applications that access the keyboard. Each screen
  22648.  group has a logical keyboard (the virtualized physical keyboard). Any
  22649.  process can access it without any preliminaries. When a user brings a screen
  22650.  group into the foreground, the logical keyboard is "bound" to the physical
  22651.  keyboard, and processes (or, more precisely, threads) in that screen group
  22652.  can read any key the user presses. Once the user moves the screen group into
  22653.  the background, the OS/2 operating system blocks any threads reading the
  22654.  keyboard in that screen group. OS/2 provides the logical keyboard
  22655.  automatically.
  22656.  
  22657.  Each KBD routine requires a keyboard handle. Like VIO, the default keyboard
  22658.  handle is 0, and an application's keyboard thread can call any KBD service
  22659.  with a handle of 0. There is no need to open a handle as long as the
  22660.  application isolates keyboard I/O to one thread (regardless of what else the
  22661.  thread may be doing). If the process is going to spread keyboard input among
  22662.  multiple threads, then it will have to perform some kind of synchronization.
  22663.  
  22664.  Unlike VIO or MOU however, KBD allows you to create multiple logical
  22665.  keyboard buffers for each session. Thus, you can have a process open an
  22666.  additional logical keyboard handle and perform its own keyboard input. You
  22667.  can use this capability when you want a program to perform keyboard input
  22668.  through a handle other than the one provided to the screen group. You can
  22669.  use the KbdOpen function to open a new logical keyboard and obtain a handle
  22670.  to access it.
  22671.  
  22672.  When you associate a logical keyboard handle with the physical keyboard, the
  22673.  handle has the keyboard focus. Processes in the same screen group can give
  22674.  or get the keyboard focus when necessary. To request the keyboard focus from
  22675.  another process in the same screen group, you can call KbdGetFocus. Note
  22676.  that you can only call KbdGetFocus in the foreground session; it will block
  22677.  if called from a background thread and it will remain blocked until the
  22678.  thread is in the foreground. KbdGetFocus will also block when another
  22679.  process has the focus and will not obtain the focus until the other process
  22680.  releases it. You can release the keyboard focus with KbdFreeFocus.
  22681.  
  22682.  Obtaining and granting the keyboard focus forces processes in the same
  22683.  session to use the keyboard on a serial basis: only one may get the keyboard
  22684.  focus at a time. If, however, OS/2 is blocking more than one thread in a
  22685.  session with KbdGetFocus, there is no way to predict which one will get the
  22686.  keyboard focus when it is released. If there are no KbdGetFocus calls
  22687.  pending when a thread calls KbdFreeFocus, the physical keyboard reverts to
  22688.  the default keyboard buffer.
  22689.  
  22690.  As I mentioned earlier, you don't have to open a keyboard handle; the
  22691.  default handle will usually do. A process that uses the default logical
  22692.  keyboard is, however, vulnerable to the interference of other processes in
  22693.  the same screen group that may also use the default handle. For instance,
  22694.  suppose one program asynchronously runs another program via DosExecPgm. If
  22695.  both processes simultaneously attempt to interact with the default keyboard,
  22696.  the results will be unpredictable. To protect against this, you can open a
  22697.  new handle, obtain the keyboard focus, and begin interacting with the
  22698.  keyboard. When you are done, you should release the keyboard focus and close
  22699.  the handle.
  22700.  
  22701.  There is no need for the process to worry about the integrity of keyboard
  22702.  data when each process uses its own logical keyboard handle. Incidentally,
  22703.  the OS/2 operating system uses KbdOpen and KbdGetFocus to obtain a default
  22704.  logical keyboard handle for each new session (which the session references
  22705.  with the default handle, 0). When you close a session, OS/2 deletes the
  22706.  logical keyboard that corresponds to the default handle.
  22707.  
  22708.  In summary, if your application performs keyboard input in only one of its
  22709.  threads or if your application is not going to share the keyboard with
  22710.  another application in the same session, you won't have to worry about
  22711.  keyboard focus. Otherwise, you should consider it. The same is true for PM.
  22712.  It will synchronize the access to the keyboard among the applications
  22713.  running in its screen group.
  22714.  
  22715.  Scan Codes
  22716.  
  22717.  As you probably know, the PC keyboard does not generate ASCII codes for the
  22718.  letters shown on the keys. Indeed, the keyboard does not know which
  22719.  characters are associated with which keys. Each time a key is pressed, the
  22720.  keyboard generates a value that corresponds to the key's position on the
  22721.  keyboard, called a scan code. By using scan codes, you can use different
  22722.  keyboards and easily adapt them to foreign languages. In addition, scan
  22723.  codes let you develop and use alternative keyboard layouts, such as the
  22724.  Dvorak keyboard.
  22725.  
  22726.  When a key is pressed, the keyboard generates an interrupt in the main
  22727.  system unit of the computer. This causes the OS/2 operating system to
  22728.  execute a routine that reads the scan code of the key you pressed from the
  22729.  keyboard port. The same is true when you release the key: the keyboard sends
  22730.  a release code (consisting of the scan code plus 128) to the system unit.
  22731.  The key press and release are sometimes called make and break interrupts,
  22732.  respectively.
  22733.  
  22734.  Each key is identified by its scan code (note that there are no scan codes
  22735.  of 0). Under the MS-DOS(R) operating system, a PC's ROM-BIOS translates the
  22736.  scan code into a key. Under OS/2, a routine translates a scan code into a
  22737.  character via a translation table. When it finishes the translation, OS/2
  22738.  places the scan code and the character into the keyboard buffer for the
  22739.  foreground process's screen group. Then the foreground application can
  22740.  retrieve it from there with a call to one of the KBD routines.
  22741.  
  22742.  Other keys may influence the generation of a particular ANSI character code.
  22743.  There are two groups of these keys: instance keys--Alt, Shift, Ctrl,
  22744.  and SysRq, and lock keys-- CapsLock, NumLock, ScrollLock, and Ins. The
  22745.  KBD routines use shift states to indicate the depression of a key in the
  22746.  first group and whether the user has toggled a key in the second group. OS/2
  22747.  uses an extended ASCII code set to incorporate the input from function keys,
  22748.  numeric keypad keys, and keys pressed with Alt-Ctrl, for a total of 256
  22749.  ASCII keystrokes. It can also support the double-byte character sets used by
  22750.  some foreign languages.
  22751.  
  22752.  Some keystrokes, such as the function keys and arrow keys, do not have an
  22753.  ASCII equivalent. For these keystrokes, the character value returned is
  22754.  either 0 or E0H after translation. You can safely use these two values to
  22755.  indicate the presence of a non-ASCII key.
  22756.  
  22757.  Although you must use the scan code when OS/2 returns a character code of
  22758.  either 0 or E0H, the latter indicates that the key is unique to an enhanced
  22759.  keyboard. For instance, the two sets of arrow keys on an enhanced keyboard
  22760.  generate the same scan codes, but a character code of E0H indicates the
  22761.  arrow key is from the independent arrow-key keypad. The same is true for
  22762.  other keys duplicated on the enhanced keyboard. Therefore, you can
  22763.  distinguish them in your program if you wish; that is something you usually
  22764.  cannot do with most high-level language keyboard functions.
  22765.  
  22766.  Cooked vs. Raw Mode
  22767.  
  22768.  OS/2 offers two flavors of keyboard input: cooked (or ASCII) and raw (or
  22769.  binary) mode. In cooked mode (the default), OS/2 will translate some
  22770.  keyboard characters, as shown in Figure 1. In addition, it treats the
  22771.  Carriage-Return character as an end-of-input character that is not passed
  22772.  back from the keyboard. The input function you use will determine whether
  22773.  the character is echoed to the screen and whether the extended keyboard keys
  22774.  and editing keys are used.
  22775.  
  22776.  In raw mode, OS/2 places each keyboard character in the foreground screen
  22777.  group's keyboard buffer without modification. In addition, in this mode OS/2
  22778.  never echoes the characters to the screen. In either mode, the standard
  22779.  operating system keys are trapped by OS/2. These include Ctrl-Esc, Alt-Esc,
  22780.  Ctrl-Alt-Del, Ctrl-Alt-NumLock, and PrtSc.
  22781.  
  22782.  Figure 1
  22783.  
  22784.  In cooked input mode, OS/2 will translate the keys listed here, and the
  22785.  application will not receive them. Each key is given with a description of
  22786.  the system action associated with it. In each case it affects only the
  22787.  program currently accepting keyboard input.
  22788.  
  22789.  ^S      Pauses or continues the execution of the program.
  22790.  
  22791.  ^P      Toggles the printer echo.
  22792.  
  22793.  ^C    Terminates the program.
  22794.  
  22795.  ^Break    Terminates the program.
  22796.  
  22797.  TAB    Inserts eight characters onto the screen at the cursor position.
  22798.  
  22799.  ^PrtSc    Dumps the input screen to the print spooler.
  22800.  
  22801.  KBD Data Structures
  22802.  
  22803.  You can use three principal data structures to access the KBD subsystem. The
  22804.  first, KBDKEYINFO (shown in Figure 2), retrieves characters from the
  22805.  keyboard buffer. As previously described, the chChar field is 0 or E0H when
  22806.  you press a non-ASCII key. In addition, this structure contains the keyboard
  22807.  scan code for the key, a byte that indicates the shift key state, and a time
  22808.  stamp (in milliseconds). An additional structure member, fbStatus, indicates
  22809.  a change in the shift key status (even if a key was not pressed) and signals
  22810.  whether a character is only the first of two bytes in a foreign language
  22811.  character set.
  22812.  
  22813.  The second structure you can use, KBDINFO shown in Figure 3, retrieves and
  22814.  sets the keyboard state. The fsMask member of this structure lets you change
  22815.  the input mode (raw or cooked), whether or not character echo is on, and
  22816.  lets you indicate which keyboard flags and settings you are changing. You
  22817.  can also use this structure to set the turnaround character. Although the
  22818.  turnaround character is usually a Carriage Return by default, it can be any
  22819.  character. Note that the cb member of this structure must be set to the size
  22820.  of the structure, thus allowing this structure to expand in the future.
  22821.  Finally, you can use the STRINGINBUF structure (see Figure 4) for reading in
  22822.  whole lines of input.
  22823.  
  22824.  Figure 2
  22825.  
  22826.  typedef struct _KBDKEYINFO
  22827.  {
  22828.  unsigned char chChar;    /* ASCII code         */
  22829.  unsigned char chScan;    /* scan code          */
  22830.  unsigned char fbStatus;    /* status code        */
  22831.  unsigned char bNlsShift;    /* reserved, 0        */
  22832.  unsigned fsState;    /* shift key state    */
  22833.  unsigned long time;    /* time in mseconds   */
  22834.  } KBDKEYINFO;
  22835.  
  22836.  The fsState bit settings:
  22837.  
  22838.  Bit     Mask     Meaning when bit is set
  22839.  
  22840.   0    0001    Right Shift pressed
  22841.   1    0002    Left Shift pressed
  22842.   2    0004    Ctrl key pressed
  22843.   3    0008    Alt key pressed
  22844.   4    0010    ScrollLock on
  22845.   5    0020    NumLock on
  22846.   6    0040    CapsLock on
  22847.   7    0080    INS on
  22848.   8    0100    Left Ctrl key pressed
  22849.   9    0200    Left Alt key pressed
  22850.  10    0400    Right Ctrl key pressed
  22851.  11    0800    Right Alt key pressed
  22852.  12    1000    ScrollLock pressed
  22853.  13    2000    NumLock pressed
  22854.  14    4000    CapsLock pressed
  22855.  15    8000    SysRq key pressed
  22856.  
  22857.  Figure 3
  22858.  
  22859.  typedef struct _KBDINFO
  22860.  {
  22861.  unsigned cb;    /* structure length    */
  22862.  unsigned fsMask;    /* keyboard modes      */
  22863.  unsigned chTurnAround;    /* end-of-input char   */
  22864.  unsigned fsInterim;    /* interim char flag   */
  22865.  unsigned fsState;    /* shift key state     */
  22866.  } KBDINFO;
  22867.  
  22868.  The fsMask bit settings:
  22869.  
  22870.  Bit     Mask    Meaning when bit set
  22871.  
  22872.   0    0001    Echo on
  22873.  
  22874.   1    0002    Echo off
  22875.  
  22876.   2    0004    Raw mode
  22877.  
  22878.   3     0008    Cooked mode
  22879.  
  22880.   4    0010    Shift state to be changed
  22881.  
  22882.   5    0020    Interim flags to be changed
  22883.  
  22884.   6    0040    Turnaround char to be changed
  22885.  
  22886.   7    0080    Length of turnaround character (1 then 2 bytes,
  22887.  
  22888.          0 then 1 byte)
  22889.  
  22890.  The fsState bit settings:
  22891.  
  22892.  Bit     Mask    Meaning when set
  22893.  
  22894.   0    0001    Right Shift pressed
  22895.  
  22896.   1    0002    Left Shift pressed
  22897.  
  22898.   2    0004    Ctrl key pressed
  22899.  
  22900.   3    0008    Alt key pressed
  22901.  
  22902.   4    0010    ScrollLock on
  22903.  
  22904.   5    0020    NumLock on
  22905.  
  22906.   6    0040    CapsLock on
  22907.  
  22908.   7    0080    INS on
  22909.  
  22910.   8-15    reserved and always 0
  22911.  
  22912.  Figure 4
  22913.  
  22914.  typedef struct _STRINGINBUF
  22915.  {
  22916.  unsigned cb;    /* buffer length    */
  22917.  unsigned cchIn;    /* bytes read       */
  22918.  } STRINGINBUF;
  22919.  
  22920.  KBD API Functions
  22921.  
  22922.  If you've read previous installments in this series, you'll be familiar with
  22923.  the OS/2 keyboard API functions (see Figure 5). Recall that we've used two
  22924.  of the better known functions, KbdCharIn and KbdStringIn, in our example
  22925.  programs. Generally, you'll find the keyboard API is simpler and easier to
  22926.  use after using VIO; that's because there aren't nearly as many ways to
  22927.  obtain keyboard input as there are to write to the video output.
  22928.  
  22929.  Whenever possible, limit keyboard I/O to one thread, regardless of whether
  22930.  that thread carries out other activities. In general, it is simpler to
  22931.  create one thread whose sole purpose is to interface with the keyboard,
  22932.  which means that it will block when there is no keyboard activity. Then that
  22933.  thread can communicate with the other threads via some form of interprocess
  22934.  communication (like a semaphore).
  22935.  
  22936.  Figure 5
  22937.  
  22938.      KbdCharIn    returns keyboard character (ascii, scan code) and shift
  22939.  state
  22940.  
  22941.      KbdClose    closes logical keyboard
  22942.  
  22943.  *    KbdDeRegister    deregisters alternate subsystem
  22944.  
  22945.      KbdFlushBuffer    flushes keyboard buffer for screen group
  22946.  
  22947.      KbdFreeFocus    frees keyboard focus
  22948.  
  22949.      KbdGetCp    retrieves code page identifier
  22950.  
  22951.      KbdGetFocus     retrieves logical keyboard focus
  22952.  
  22953.      KbdGetStatus     retrieves keyboard status
  22954.  
  22955.      KbdOpen    opens logical keyboard
  22956.  
  22957.      KbdPeek    returns status of next character in keyboard buffer
  22958.  
  22959.  *    KbdRegister    registers alternate keyboard subsystem
  22960.  
  22961.      KbdSetCp    sets code page identifier
  22962.  
  22963.      KbdSetCustXt    installs alternate translation table
  22964.  
  22965.      KbdSetStatus     modifies keyboard status
  22966.  
  22967.      KbdStringIn    reads line from keyboard
  22968.  
  22969.      KbdSynch     serializes keyboard access
  22970.  
  22971.      KbdXlate     translates scan codes to Ascii
  22972.  
  22973.  *Not allowed or does nothing if calling process is run in PM window.
  22974.  
  22975.  KbdCharIn
  22976.  
  22977.  The most commonly used function in the KBD API is KbdCharIn. This function
  22978.  takes three parameters: a pointer to the KBDKEYINFO structure, a wait/nowait
  22979.  parameter, and a keyboard handle (usually 0). If the wait parameter is
  22980.  IO_WAIT, the function will block the calling thread until keyboard input is
  22981.  available; otherwise it will return immediately. Either way you'll need to
  22982.  use the fbStatus member of the KBDKEYINFO structure to see if the user
  22983.  pressed a key. Note that KbdCharIn does not echo the characters it returns.
  22984.  
  22985.  Probably the biggest mistake made by the new OS/2 programmer is to write a
  22986.  function that calls KbdCharIn in a loop with the wait flag set to IO_NOWAIT.
  22987.  Since the function will return immediately regardless of whether a character
  22988.  is available, it will burn up tremendous amounts of CPU time as it cycles
  22989.  through the loop. Instead, it is wiser to dedicate one thread to keyboard
  22990.  I/O and have that thread block on a call to KbdCharIn with the wait flag set
  22991.  to IO_WAIT. When the thread does return, it can use a semaphore or some
  22992.  other device to notify the main thread that it has received a character from
  22993.  the keyboard. Alternatively, it can insert the character in an application
  22994.  buffer and return to KbdCharIn.
  22995.  
  22996.  KbdPeek
  22997.  
  22998.  Another popular function is KbdPeek. It is similar to the kbhit standard
  22999.  library function, since you can use it to interrogate the status of the
  23000.  keyboard without removing any characters waiting in the keyboard buffer. It,
  23001.  however, not only tells you whether a keypress is present, but can return
  23002.  the keyboard status and the value of the next character (if there is one) as
  23003.  well. Like KbdCharIn, it requires a KBDKEYINFO structure and a keyboard
  23004.  handle as parameters.
  23005.  
  23006.  Keyboard Status
  23007.  
  23008.  KbdGetStatus and KbdSetStatus can return and set the keyboard status
  23009.  information via the KBDINFO structure. To set part of the keyboard status,
  23010.  you must first set the proper bit in fsMask in the KBDINFO structure to
  23011.  indicate what type of function you are changing. Then you can set the value
  23012.  of the related parameter. For instance, to turn on the CapsLock key, first,
  23013.  you must set bit 4 in fsMask and bit 6 in fsState. Then a call to
  23014.  KbdSetStatus will toggle this key on. For switching between cooked and raw
  23015.  input modes and character echo on and off, you need only set the bits in
  23016.  fsMask.
  23017.  
  23018.  Note that these changes are local to the session in which the calling thread
  23019.  is running: they do not affect the keyboard status of other sessions. You
  23020.  should call KbdGetStatus first to initialize the structure so the rest of
  23021.  the flags in KBDINFO will be unchanged.
  23022.  
  23023.  KbdStringIn
  23024.  
  23025.  The KbdStringIn function reads a string of characters from the keyboard
  23026.  buffer. It requires four parameters: the buffer into which the characters
  23027.  will be placed, the STRINGINBUF structure, a wait/nowait flag, and the
  23028.  keyboard handle. You can read up to 255 characters with this function.
  23029.  
  23030.  KbdStringIn performs differently, depending on which input mode is in place
  23031.  when you call it. In raw mode, if you set the wait flag to IO_WAIT, the
  23032.  function will read characters from the keyboard buffer until your buffer is
  23033.  full (based on the buffer size placed in STRINGINBUF). If the wait flag is
  23034.  IO_NOWAIT, the function will read however many characters are waiting in the
  23035.  keyboard buffer (but not more than will fit into your buffer) and return
  23036.  immediately. KbdStringIn will not echo the characters to the display and you
  23037.  won't be able to use the editing keys in raw mode. The function places
  23038.  extended ASCII keys in the buffer as a 2-byte sequence of 00h or E0h
  23039.  followed by the scan code.
  23040.  
  23041.  In cooked mode, you must use IO_WAIT for the wait flag. The function will
  23042.  echo each character to the screen unless the echo flag is off. You can use
  23043.  the standard editing keys, but the function will ignore all other extended
  23044.  keys. TABS are expanded to spaces on the screen, using eight-column tab
  23045.  stops; but the function will leave them in the buffer as 09h. You must
  23046.  terminate input to KbdStringIn in cooked mode with ENTER, which the function
  23047.  will place in the buffer as 0Dh. When the buffer contains one character less
  23048.  than the requested buffer length, the function will discard additional
  23049.  keystrokes and sound a warning beep until the user presses ENTER.
  23050.  
  23051.  The cchIn member of the STRINGINBUF structure will indicate the number of
  23052.  characters placed in the buffer upon return from the function. In cooked
  23053.  mode, however, you can also set cchIn to the number of characters already in
  23054.  the buffer before you call the function. This allows you to use the data
  23055.  already in your buffer as an editing template.
  23056.  
  23057.  Mouse Interface
  23058.  
  23059.  Although the screen and keyboard are the traditional mechanisms for user
  23060.  interaction on microcomputer systems, the mouse is fast becoming an
  23061.  indispensable accessory. Nowhere is this more true than in the OS/2
  23062.  environment, where the PM graphical interface seamlessly lends itself to a
  23063.  mouse-based operation. OS/2 programs that do not support the mouse are
  23064.  already rare; they are all character-based applications ported from DOS
  23065.  before the release of OS/2 Version 1.1.
  23066.  
  23067.  OS/2 offers two sets of mouse interface services: the core API services and
  23068.  the PM routines. The PM mouse services make it easy to write programs that
  23069.  use the mouse, since the program doesn't have to know you are using a mouse.
  23070.  You can, however, use the core API services to take advantage of the mouse
  23071.  interface without having to write a PM program.
  23072.  
  23073.  The mouse device interface gives the appearance of being tightly linked to
  23074.  the video screen. You can think of the screen and your desktop as grids. The
  23075.  mouse movements on the desktop grid control parallel movements of the mouse
  23076.  pointer upon the screen. Applications that use and manipulate a mouse may
  23077.  concern themselves with correlating the mouse and the mouse pointer, the
  23078.  definition of restricted areas, and, possibly, the shape of the mouse
  23079.  pointer itself.
  23080.  
  23081.  Mouse Drivers
  23082.  
  23083.  The OS/2 environment uses two device drivers for its mouse interface:
  23084.  POINTDD.SYS to draw the mouse pointer and MOUSEA0.SYS to interface with the
  23085.  mouse itself. OS/2 loads POINTER$, the pointer draw driver from POINTDD.SYS.
  23086.  POINTER$ draws and maintains the mouse pointer on screen and interfaces with
  23087.  the mouse driver to determine where to draw the pointer. It represents the
  23088.  motion of the mouse on screen by determining the mouse's new position,
  23089.  erasing its old image at its previous position, and redrawing the new image
  23090.  at the new position. POINTER$ is only operational in text mode. You must
  23091.  disable the driver before switching into graphics mode. Ordinary graphics
  23092.  applications must do much of the mouse interface work themselves, including
  23093.  monitoring the movement of the mouse, supplying a pointer image, and drawing
  23094.  the pointer on the screen. The OS/2 Presentation Manager takes care of these
  23095.  chores for applications running in its screen group.
  23096.  
  23097.  OS/2 loads the character device driver, MOUSE$, from MOUSEXX.SYS. There are
  23098.  several different versions of MOUSEXX.SYS; which one you load will depend on
  23099.  which mouse you use. OS/2 supports the full range of popular mice, including
  23100.  the entire Microsoft(R) line and the IBM(R) PS/2(R) mouse. Note that whereas
  23101.  the standard IBM/Microsoft mouse has two buttons, other models have one,
  23102.  two, or three buttons, all of which OS/2 can support. The MOUSE$ driver
  23103.  services the mouse interrupts and keeps track of the mouse position and
  23104.  button states. It also provides the device I/O interface used by the Basic
  23105.  Mouse Subsystem (BMS).
  23106.  
  23107.  You can configure the mouse driver parameters and the mouse driver itself by
  23108.  modifying its entry in CONFIG.SYS. For instance, my Microsoft Inport Mouse
  23109.  uses the following entry in CONFIG.SYS:
  23110.  
  23111.  
  23112.  
  23113.  DEVICE=C:\OS2\MOUSEA04.SYS
  23114.  
  23115.  
  23116.  
  23117.  You can add parameters after the driver filename that specify which serial
  23118.  port to use (if it's a serial mouse), the size of the mouse event queue
  23119.  (explained below), and the mouse operational mode. For instance,
  23120.  
  23121.  DEVICE=C:\OS2\MOUSEXX.SYS SERIAL=COM2,QSIZE=50,MODE=R
  23122.  
  23123.  will set this driver to use communications port 2, with a queue size of 50.
  23124.  The MODE parameter will tell OS/2 to use this mouse driver in real mode.
  23125.  
  23126.  The SERIAL= parameter will force a serial mouse driver to use the specified
  23127.  communications port; the default is COM1. QSIZE= will set the queue to hold
  23128.  up to a specified number of mouse event packets (the default is 10). Note
  23129.  that each packet requires 10 bytes of storage. MODE=P will set the driver to
  23130.  operate only in protected mode; MODE=R will do the same for real mode. The
  23131.  default MODE is both real and protected mode.
  23132.  
  23133.  Mouse DLLs
  23134.  
  23135.  The OS/2 operating system supplies two sets of DLLs that provide mouse
  23136.  interface support to the API. BMCALLS.DLL contains the BMS, which implements
  23137.  the MOU services and communicates with the MOUSE$ driver. Like its
  23138.  counterpart in the keyboard subsystem, MOUCALLS.DLL is the mouse router. It
  23139.  contains entry points for the MOU API functions and passes incoming calls to
  23140.  the appropriate subsystem in each screen group. By default, this subsystem
  23141.  is BMSCALLS; you can, however, replace it wholly or in part, just as you can
  23142.  with other subsystems.
  23143.  
  23144.  Mouse Events
  23145.  
  23146.  The mouse is a read-only device. It can generate information about its
  23147.  movements and the state of the mouse buttons (pressed or released). Each
  23148.  piece of information--a mouse movement, a button press, or
  23149.  both--is called a mouse event. The mouse can generate up to 30 events
  23150.  per second. Like the keyboard, the mouse sends event information to the
  23151.  mouse driver via a hardware interrupt.
  23152.  
  23153.  OS/2 is capable of providing your application with the event information. It
  23154.  organizes the mouse information as a packet and places it in a first-in
  23155.  first-out (FIFO) queue until your program reads it. The packet includes the
  23156.  current position of the mouse pointer and the state of the mouse buttons.
  23157.  Although the queue is not very large, a considerable number of packets can
  23158.  be generated before your program reads them. If this happens, newer packets
  23159.  will overwrite older ones to make room in the queue. This is not a problem,
  23160.  since your application will usually want to receive the most timely mouse
  23161.  information. Unlike the keyboard, it is unusual that you would need to trace
  23162.  every mouse event.
  23163.  
  23164.  The OS/2 mouse services can return mouse pointer position information in two
  23165.  ways. The default method returns the row and column position of the mouse
  23166.  pointer in screen units. In text mode these correspond to character
  23167.  positions; in graphics mode they are pel coordinates. A mouse pointer cannot
  23168.  be "in between" positions. In addition, all mouse coordinates are set
  23169.  relative to the upper-left-hand corner of the screen, which is row 0, column
  23170.  0 in screen units.
  23171.  
  23172.  OS/2 can also return the mouse position information in mickey counts (a name
  23173.  taken from the cartoon character). A mickey is the smallest unit of mouse
  23174.  measurement and corresponds to a specific measurement in centimeters on the
  23175.  desktop. Mickey references use the x and y coordinates found in Cartesian
  23176.  algebra. If the y value is negative, it means you have moved the mouse away
  23177.  from you on the desk, leaving the pointer further up the screen. If the y
  23178.  value is positive, it means you have moved the mouse toward you with a
  23179.  corresponding movement of the mouse down the screen. If the x value is
  23180.  negative, both the mouse and pointer have moved left; if the x value is
  23181.  positive, both have moved right.
  23182.  
  23183.  Scaling Factors
  23184.  
  23185.  OS/2 does not force you to use a fixed relationship between mouse movements
  23186.  on the desktop and mouse pointer movements on the screen. Instead, you can
  23187.  change the scaling factors, that is, the number of mickeys you must move the
  23188.  mouse in order to change the location of the mouse pointer by 1 screen unit.
  23189.  
  23190.  If the scaling factor is 1, the pointer driver will move the mouse pointer 1
  23191.  screen unit for every mickey you move the mouse. If the scaling factor is 2,
  23192.  the pointer driver will move the mouse pointer 1 screen unit for every 2
  23193.  mickeys you move the mouse, and so on. In other words, the greater the
  23194.  scaling factor, the more you must physically move the mouse to move the
  23195.  mouse pointer on the screen. That's why it's easy to make the case that the
  23196.  choice of scaling factor is governed more by the amount of free desk space
  23197.  than by preference. The larger the scaling factor, the more desk space
  23198.  you'll need.
  23199.  
  23200.  Collision Areas
  23201.  
  23202.  The OS/2 mouse interface allows you to create collision areas. These are
  23203.  areas of the screen into which the mouse pointer cannot enter. The pointer
  23204.  driver accomplishes this simply by not drawing the mouse pointer inside the
  23205.  designated area. A text mode application can use a collision area to
  23206.  restrict mouse input from a certain part of the screen during a specific
  23207.  operation. OS/2 does not support collision areas in graphics mode, so you'll
  23208.  have to implement the collision areas yourself.
  23209.  
  23210.  PM Mouse Support
  23211.  
  23212.  The OS/2 Presentation Manager provides a set of mouse API functions that are
  23213.  more advanced than those supplied by the standard mouse subsystem (see
  23214.  Figure 6). As such, there is generally less work involved when dealing with
  23215.  the mouse interface under Presentation Manager. Indeed, PM graphics
  23216.  applications do not have to draw or move the mouse--the Presentation
  23217.  Manager will do that for them. In addition, the PM mouse interface is
  23218.  simpler, and the application does not have to keep track of the mouse
  23219.  position. Instead, PM notifies the application of the user selection of a
  23220.  menu item--the application does not have to know whether the user
  23221.  selected the item with the keyboard or the mouse. Also note that
  23222.  character-mode applications that use the mouse will generally work fine in a
  23223.  PM window--except for the few side effects described below.
  23224.  
  23225.  Figure 6
  23226.  
  23227.      MouClose    closes mouse handle
  23228.  
  23229.  *    MouDeRegister    deregisters alternate subsystem
  23230.  
  23231.  *    MouDrawPtr    displays mouse pointer
  23232.  
  23233.      MouFlushQue    flushes mouse event queue
  23234.  
  23235.      MouGetDevStatus    retrieves mouse status
  23236.  
  23237.      MouGetEventMask    retrieves mouse event mask
  23238.  
  23239.      MouGetHotKey    retrieves mouse hot key combination
  23240.  
  23241.      MouGetNumButtons    retrieves number of buttons
  23242.  
  23243.      MouGetNumMickeys    retrieves mickeys per centimeter
  23244.  
  23245.      MouGetNumQueEl    retrieves number of mouse events in queue
  23246.  
  23247.      MouGetPtrPos    retrieves mouse pointer position
  23248.  
  23249.  *    MouGetPtrShape    retrieves pointer bit mask
  23250.  
  23251.  *    MouGetScaleFact    retrieves scaling factors
  23252.  
  23253.      MouInitReal    initializes real mode mouse
  23254.  
  23255.      MouOpen    opens mouse handle
  23256.  
  23257.      MouReadEventQue    retrieves mouse event information
  23258.  
  23259.  *    MouRegister    registers alternate subsystem
  23260.  
  23261.      MouRemovePtr    removes mouse pointer (creates collision area)
  23262.  
  23263.  *    MouSetDevStatus    sets mouse device status
  23264.  
  23265.      MouSetEventMask    sets mouse event mask
  23266.  
  23267.      MouSetHotKey    sets mouse hot key
  23268.  
  23269.      MouSetPtrPos    sets mouse pointer position
  23270.  
  23271.  *    MouSetPtrShape    sets mouse pointer mask
  23272.  
  23273.  *    MouSetScaleFact    sets scale factors
  23274.  
  23275.      MouSynch    synchronizes mouse access
  23276.  
  23277.  *Not allowed or does nothing if calling process is run in PM window.
  23278.  
  23279.  Family Mode Support
  23280.  
  23281.  We haven't addressed Family Mode issues since the first OS/2 program in this
  23282.  series, "Planning and Writing a Multithreaded OS/2 Program with Microsoft
  23283.  C," MSJ (Vol. 4, No. 2). That's because we haven't had to. The moment we
  23284.  wrote the first multithreaded program, we couldn't consider Family Mode
  23285.  since DOS does not support multithreaded programs. Many of the VIO and KBD
  23286.  API services, however, have Family Mode counterparts that let you write
  23287.  bound programs that run under both OS/2 and DOS (as long as they are not
  23288.  multithreaded). The MOU services, on the other hand, are not available under
  23289.  DOS, and you'll have to use Int 33h. To develop an application that uses the
  23290.  mouse and runs under both real and protected mode, you'll have to write
  23291.  functions that call the MOU API in protected mode and Int 33h in real mode.
  23292.  
  23293.  Opening the Handle
  23294.  
  23295.  Since OS/2 does not provide a default handle for the mouse interface, as it
  23296.  does for the keyboard and screen, you must first open a mouse handle before
  23297.  your application can use the mouse. You can use the MouOpen function to open
  23298.  the mouse and receive a handle that your application can use. This will
  23299.  initialize the mouse driver for your application's session, but it will not
  23300.  display the mouse pointer. That's because when you open a mouse handle, the
  23301.  entire screen is a collision area and the mouse pointer will not appear
  23302.  until your application calls MouDrawPtr. You can close the mouse pointer
  23303.  with MouClose.
  23304.  
  23305.  The Basic Mouse Subsystem can synchronize access to the physical mouse by
  23306.  several sessions, but it cannot synchronize mouse access by multiple
  23307.  processes in the same session. The mouse threads in a screen group share the
  23308.  same mouse event queue. Thus, as with the keyboard, if your application must
  23309.  share mouse access with other processes in its screen group, you'll have to
  23310.  make provisions to synchronize this access. Unfortunately, there is no MOU
  23311.  equivalent to KbdGetFocus or KbdFreeFocus, so you'll have to perform this
  23312.  synchronization yourself. But OS/2 does provide an API service, MouSynch, to
  23313.  do this; in fact, the Presentation Manager uses this same facility to
  23314.  coordinate mouse access among the processes in its screen group.
  23315.  
  23316.  Note that like the keyboard, OS/2 blocks a background thread that tries to
  23317.  read the mouse. Thus, it is wise not only to limit mouse access to one
  23318.  thread in your program but to make mouse access the thread's raison d'etre.
  23319.  
  23320.  In text mode, it is relatively simple to access the mouse: open the mouse
  23321.  with MouOpen, make the pointer appear with MouDrawPtr, and then begin to
  23322.  evaluate the mouse events with MouReadEventQue. In a graphics mode, the
  23323.  process is slightly more complex. As I mentioned earlier, a graphics
  23324.  application must perform its own mouse manipulation. Therefore you must open
  23325.  the mouse while the application is in text mode and then disable it with
  23326.  MouSetDevStatus. You then change the video mode to graphics with VioSetMode
  23327.  and create your own mouse pointer with MouSetPtrShape. The application
  23328.  should call MouReadEventQue to interpret mouse events, but it will need to
  23329.  use MouSetPtrPos to move the pointer accordingly.
  23330.  
  23331.  Reading from the Mouse Queue
  23332.  
  23333.  As we have already mentioned, every time you move the mouse or press a mouse
  23334.  button, OS/2 will place an event information packet on the end of the mouse
  23335.  queue. You can use MouReadEventQue to read information from the queue. This
  23336.  function takes three parameters: a pointer to the MOUEVENTINFO structure, a
  23337.  wait/nowait parameter, and a mouse handle.
  23338.  
  23339.  The MOUEVENTINFO structure, shown in Figure 7, contains the information
  23340.  returned by the function. The fs member contains bits that indicate the
  23341.  current state of the mouse buttons and whether the mouse has moved. The Time
  23342.  member records the time of the event in milliseconds. You can use Time to
  23343.  determine when an event occurred relative to the previous or next event in
  23344.  the queue. This allows your application to interpret double-clicks of the
  23345.  mouse button, intervals between mouse movements, and so on. MOUEVENTINFO
  23346.  also includes the row and column coordinates of the current mouse position.
  23347.  These are in screen units, which are character rows and columns for text
  23348.  mode or pels in graphics mode.
  23349.  
  23350.  The wait/nowait parameter to MouReadEventQue allows the calling thread to
  23351.  block if there are no mouse events in the queue. Note that unlike other OS/2
  23352.  API functions, you must pass this parameter by reference instead of by value
  23353.  (that is, you must set a variable to the value and pass the address of the
  23354.  variable). Also note that this parameter uses 1 to wait and 0 to return
  23355.  immediately, which again differs from other API functions.
  23356.  
  23357.  MouGetPtrPos is an alternative to MouReadEventQue. This function will return
  23358.  the mouse coordinates, but it won't return the state of the mouse buttons.
  23359.  MouGetPtrPos uses the PTRLOC structure shown in Figure 8.
  23360.  
  23361.  The default OS/2 mouse queue size is 10. Although this should be sufficient
  23362.  in most cases, you can set a value from 1 to 100 for it by passing a
  23363.  parameter to the mouse driver specified in CONFIG.SYS. You can use
  23364.  MouGetEventMask and MouSetEventMask to screen out certain types of events.
  23365.  This is particularly useful if, for instance, you want an application to
  23366.  ignore presses of the second mouse button.
  23367.  
  23368.  You can use MouGetScaleFact to retrieve the currently used scaling factor
  23369.  and use  MouSetScaleFact to set a new scaling factor. These functions take
  23370.  two parameters: a pointer to the SCALEFACT structure and a mouse handle. The
  23371.  SCALEFACT structure, shown in Figure 9, allows you to set scaling factors
  23372.  from 1 to 32,767, although a more practical range is from 1 to about 24.
  23373.  
  23374.  Figure 7
  23375.  
  23376.  typedef struct _MOUEVENTINFO
  23377.  {
  23378.  unsigned fs;               /* event mask         */
  23379.  unsigned long Time;        /* system timestamp   */
  23380.  unsigned row;              /* pointer position   */
  23381.  unsigned col;              /* pointer position   */
  23382.  } MOUEVENTINFO;
  23383.  
  23384.  The fs bit settings (this is 0 if no event occurred):
  23385.  
  23386.  Bit        Mask        Meaning when bit set
  23387.  
  23388.   0    0001    Mouse moved, no buttons pressed
  23389.  
  23390.   1    0002    Mouse moved, button 1 pressed
  23391.  
  23392.   2    0004    No movement, button 1 pressed
  23393.  
  23394.   3    0008    Mouse moved, button 2 pressed
  23395.  
  23396.   4    0010    No movement, button 2 pressed
  23397.  
  23398.   5    0020    Mouse moved, button 3 pressed
  23399.  
  23400.   6    0040    No movement, button 3 pressed
  23401.  
  23402.   7    0080    Reserved, 0
  23403.  
  23404.  Figure 8
  23405.  
  23406.  typedef struct _PTRLOC
  23407.  {
  23408.  unsigned row;    /* pointer position    */
  23409.  unsigned col;    /* pointer position    */
  23410.  } PTRLOC;
  23411.  
  23412.  Figure 9
  23413.  
  23414.  typedef struct _SCALEFACT
  23415.  {
  23416.  unsigned rowScale;    /* vertical factor    */
  23417.  unsigned colScale;    /* horizontal factor    */
  23418.  } SCALEFACT;
  23419.  
  23420.  The Time member contains the system time (in milliseconds) at which the
  23421.  mouse event occurred. The row and column members contain the screen location
  23422.  of the mouse pointer. By default this is in screen units, but a call to
  23423.  MouSetDevStatus can change this to mickeys.
  23424.  
  23425.  KMSTATUS.C
  23426.  
  23427.  The program presented with this article, KMSTATUS.C, illustrates how you can
  23428.  use the OS/2 keyboard and mouse subsystems. When you run the program, it
  23429.  will present a display that splits the screen into two sections (see Figure
  23430.  10). The upper section contains mous