home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / msj / msj6.txt < prev   
Encoding:
Text File  |  2013-11-08  |  173.9 KB  |  3,807 lines

  1.  Microsoft Systems Journal Volume 6
  2.  
  3.  ────────────────────────────────────────────────────────────────────────────
  4.  
  5.  Volume 6 - Number 1
  6.  
  7.  ────────────────────────────────────────────────────────────────────────────
  8.  
  9.  
  10.  
  11.  Adapting Extended Processes to the Cooperative Multitasking of Microsoft
  12.  Windows
  13.  
  14.  William S. Hall
  15.  
  16.  Programming a lengthy process in the Microsoft Windows graphical environment
  17.  requires unique considerations. Unlike single-tasking or preemptive
  18.  multitasking operating environments, Windows1 uses message-driven,
  19.  cooperative multitasking to perform tasks. Windows programs do not execute
  20.  until a message is received; once received, the message must be processed
  21.  quickly so that control of the CPU can be relinquished to permit other
  22.  Windows programs to run. Performing a time-consuming process by coding a
  23.  single uninterrupted thread of execution is completely unsatisfactory
  24.  because Windows will never run any other task and will appear frozen to the
  25.  user during that time.
  26.  
  27.  Fortunately, large tasks can often be broken down into smaller ones, each of
  28.  which can be quickly executed whenever the program is allowed to run. If
  29.  some means can be found for gaining control at appropriate intervals and
  30.  dispatching each task in the proper sequence, then it should be possible to
  31.  implement an extended process smoothly in Windows.
  32.  
  33.  Asynchronous file transfer between two computers serves as a good example of
  34.  an extended process, because it can take hours over conventional
  35.  communication lines. This article demonstrates how to coordinate an extended
  36.  process into Windows using the popular Kermit file transfer protocol. One
  37.  common approach to writing a Kermit program involves breaking the protocol
  38.  into a sequence of tasks controlled by a finite state machine. I'll describe
  39.  modifications of this approach that fit the requirements of Windows and
  40.  illustrate how the program can be made to schedule its next task without the
  41.  use of a timing mechanism.
  42.  
  43.  An implementation of Kermit that can be easily integrated into a Windows
  44.  terminal emulator is provided, as well as a sample terminal program that
  45.  uses this implementation. The program is capable of sending and receiving
  46.  files in text and binary formats over 7- and 8-bit wide data paths while
  47.  employing any of three methods of error detection and run-length encoding
  48.  for efficiency. Wildcards can be used to transfer groups of files in a
  49.  single operation. Although server mode and extensions such as long and
  50.  attribute packets are not included, the program is quite complete.
  51.  
  52.  A total of 40 files are used to build Kermit and the two terminal emulators
  53.  described here. Partial listings taken from the Kermit and terminal programs
  54.  are included with this article. The complete source code needed to build
  55.  Kermit, the simple Windows terminal emulator, and the same emulator with
  56.  embedded Kermit is available on any MSJ bulletin board.
  57.  
  58.  Realizing Kermit's Session Layer in Windows
  59.  
  60.  In a Kermit session, information is exchanged by encapsulating blocks of
  61.  data in various types of packets that are normally less than 100 bytes in
  62.  length, unless an extended packet type is being used (see Figure 1).
  63.  Although Kermit is not a layered protocol as specified in the ISO open
  64.  system standards, most versions of Kermit are written with these layers in
  65.  mind to isolate functionality and make it easy to extend and maintain.
  66.  
  67.  The Kermit session layer acts as the basic controlling mechanism for the
  68.  protocol. When sending or receiving a file, the session layer is driven by
  69.  the packet type received from the remote Kermit. This session layer can be
  70.  thought of as a finite state machine.
  71.  
  72.  A public-domain tool called Wart has been developed for Kermit that allows a
  73.  programmer to describe such state machines very elegantly (see the sidebar
  74.  "Building a Finite State Machine with Wart" ). The Wart tool converts the
  75.  protocol description into a table-driven case statement, which is entered at
  76.  run time by calling the wart() function. The current state is maintained in
  77.  a static variable; thepacket type is obtained by the wart() function's call
  78.  to the transport layer function input() (the Kermit code implements its
  79.  protocol description using wart() in the file WNKERM.W, as shown in Figure
  80.  2). In turn, input() calls a data link layer function, rpack (see Figure 3),
  81.  to obtain a complete packet. Figure 4 diagrams the various states of the
  82.  Kermit protocol implemented in WNKERM.W.
  83.  
  84.  In Kermit implementations written for single-tasking or preemptive
  85.  multitasking operating systems, once wart() is entered it never returns
  86.  until the entire session is complete or has been aborted. Likewise, input()
  87.  does not return to wart() until a properly formed packet with the correct
  88.  sequence number has been found. In turn, rpack does not return to input()
  89.  until a complete packet with a correct checksum has been obtained or a
  90.  time-out (usually of several seconds) has elapsed.
  91.  
  92.  Of course, none of this waiting around is acceptable in Windows. Suppose
  93.  wart() is called from a Windows program as the result of a message. If
  94.  wart() runs as above, no other Windows program can run until the file
  95.  transfer has completed. Even if wart() returns after the action associated
  96.  with each state has been executed, long delays could still be experienced
  97.  while rpack tries to read in a complete packet from the communications line.
  98.  In fact, a complete packet might never arrive.
  99.  
  100.  On the other hand, the actions associated with each state complete without
  101.  significant delay because they involve reading or writing small chunks of
  102.  data from a file, encoding or decoding this data, forming it into a packet,
  103.  and writing it out to the communications buffer. The solution is to violate
  104.  the layering principle and allow the session layer to recognize an
  105.  incomplete packet whose associated action is simply to return from wart().
  106.  This simple addition to the packet types plus a mechanism to build a packet
  107.  incrementally from successive reads of the communications line allows
  108.  Windows to release the CPU to other programs in a timely fashion.
  109.  
  110.  This is how it works. Periodically the Windows procedure is entered with a
  111.  programmer-defined message that calls wart(), which then calls input(). If a
  112.  packet is ready, the action for that state is executed and another call to
  113.  input() is made. This time input() most likely returns an incomplete packet
  114.  and wart() returns control to Windows. Other tasks can then run; the result
  115.  is a nearly seamless multitasking functionality that handles the file
  116.  transfer almost transparently.
  117.  
  118.  Of course, the way wart() is scheduled to run has not been explained. But
  119.  first, let's look at how the data link layer is handled.
  120.  
  121.  The Data Link Layer
  122.  
  123.  After the input() function calls rpack in the data link layer to get the
  124.  next packet, rpack calls getpacket, which parses the input stream into the
  125.  packet fields (see Figure 3). The getpacket function frames a packet by
  126.  detecting the start-of-packet character; the length of the data field is
  127.  used to determine when a complete packet has arrived. On any single call to
  128.  getpacket, the input stream may not contain enough data to build a complete
  129.  packet, and it may take several calls to this function before a packet can
  130.  be delivered.
  131.  
  132.  This means that getpacket must be designed so that when it exhausts the
  133.  current data stream, it remembers how far along it has progressed and the
  134.  point from which it must continue when called again with more data. A state
  135.  machine implementation is again implied; it is easily realized by a case
  136.  statement because of the simplicity of getpacket. The state variable itself
  137.  is maintained in a globally visible structure that also contains the other
  138.  fields of the current packet. Each time getpacket is called, the case
  139.  statement is executed from the current state and returns when the buffer is
  140.  exhausted. This way, getpacket and its caller rpack do not need to wait for
  141.  more data to arrive from the communications line, and input can return
  142.  immediately to wart() with an incomplete packet type.
  143.  
  144.  Waking Up the Session Layer
  145.  
  146.  The missing piece in the above scenario is the scheduling of the session
  147.  layer. An obvious solution is to use a timer and place the call to wart() on
  148.  this thread. A timer, however, does not adjust to changing load conditions
  149.  and extra effort must be made to set the timing interval according to the
  150.  current baud rate.
  151.  
  152.  A more satisfactory method is to replace the conventional GetMessage call in
  153.  the Windows message loop with PeekMessage. Whereas GetMessage does not
  154.  return to the program until a message has been placed into its queue,
  155.  PeekMessage always returns whether there is a message or not.
  156.  
  157.  To use PeekMessage, your program processes any Windows messages that can be
  158.  pulled from the queue, or, if there are none, it does something else for a
  159.  short period. In the design used here, the communications port is polled
  160.  during this time, and any pending data is loaded into a local buffer. Then a
  161.  programmer-defined message is posted back to the window. The next call to
  162.  PeekMessage retrieves this message and the associated action is to call
  163.  wart(). Getpacket is entered via calls to input and rpack with the buffer
  164.  that was filled when the communications port was polled by PeekMessage. If a
  165.  complete packet is built, the next action in the session layer state machine
  166.  is executed. Otherwise, wart() sees an incomplete packet, and returns to
  167.  Windows. Figure 5 shows the message loop and excerpts of the WinProc used in
  168.  the sample terminal emulator.
  169.  
  170.  Sample Application
  171.  
  172.  The complete source code for a Windows-based terminal emulator with embedded
  173.  Kermit support (see Figure 6) can be found on any MSJ bulletin board. You'll
  174.  need the Microsoft Windows Software Development Kit (SDK) Version 3.0 and
  175.  Microsoft C Version 5.1 or 6.0 to build the application. A typical Windows
  176.  program, the terminal program consists of three parts: source, resources,
  177.  and definitions. Of course, the additions needed to add Kermit functionality
  178.  affect each of these three components.
  179.  
  180.  For the most part, the Kermit module is independent of the terminal source
  181.  code. This module is a library to be added at link time. Of course the
  182.  terminal program cannot be entirely ignorant of certain Kermit functions,
  183.  and Kermit itself needs to be aware of certain variables such as the main
  184.  window and instance handles, the current communications channel, and the
  185.  location of the buffer used for reading data from the port. However, all of
  186.  these details can be handled fairly easily in the terminal source. By using
  187.  conditional compilation, the changes to the original code become clearly
  188.  marked, making adaptation to other terminal programs fairly straightforward.
  189.  
  190.  Trying to maintain this same degree of isolation with respect to resources
  191.  is another matter. Kermit requires a user interface, so a string table, menu
  192.  item, and several dialog boxes have been added. Although the resource
  193.  compiler supplied with Version 3.0 of the SDK is a definite improvement over
  194.  its 2.x predecessor, it is still difficult to maintain multiple compilations
  195.  with a single source. In the end, it is necessary to build one RC source
  196.  file for the plain terminal emulator (without Kermit), another for the
  197.  emulator with Kermit, and then #include other resource files as needed.
  198.  
  199.  Of course, the additional Kermit code and resources also affect the DEF file
  200.  because they require more function exports and segment names. But since it
  201.  is possible to have more than one SEGMENTS and EXPORTS section in a single
  202.  DEF file, it is quite simple to create a combined DEF file by concatenation.
  203.  Of course, segment names as well as function export numbers should be
  204.  unique.
  205.  
  206.  There are three make files that control the  sources. The first, WNTERM,
  207.  simply builds the original terminal program, WNTERM.EXE. It is provided so
  208.  you can see clearly what has to change when the Kermit code is added. The
  209.  second, WNKERM, makes the Kermit library and compiles the Kermit resources.
  210.  The third make file, WNKTERM, builds the terminal with embedded Kermit
  211.  support, WNKTERM.EXE.
  212.  
  213.  The Kermit Header File
  214.  
  215.  Each of the Kermit modules as well as the terminal code that references
  216.  Kermit variables or constants must include the header WNKERM.H (see Figure
  217.  7). It consists of Kermit function prototypes, a menu, resource strings,
  218.  general manifests, and a number of structures associated with dialog boxes
  219.  or the protocol. All Kermit variables are defined in this header file. At
  220.  least one module in the terminal code should include WNKERM.H to declare the
  221.  data. To avoid possible conflicts with the terminal code, all variables and
  222.  functions begin with the prefix krm. All manifests include the string KRM_.
  223.  Thus, IDS_KRM_XXX names a string stored in the resource file, IDM_KRM_XXX, a
  224.  menu item, and IDD_KRM_XXX, a dialog box control. Strings use a reference
  225.  value, and other strings' manifests base themselves from this reference. The
  226.  base value is set to 10,000, but can be changed to avoid possible conflict
  227.  with the terminal code. The same is true of menus.
  228.  
  229.  The Kermit code expects certain variables to be available from the terminal.
  230.  These are copied to Kermit variables at initialization. One of these, the
  231.  handle of the main window, is good for the lifetime of the program. Other
  232.  values may change periodically. For example, the communications port
  233.  identifier (cid) may vary if the user selects a different port. Because of
  234.  this, the Kermit code uses a pointer to reference the cid. Kermit must also
  235.  be able to reference a linear character buffer along with its current count,
  236.  so the count and the start of the buffer are kept as pointers by Kermit.
  237.  Kermit also expects that there are routines in the terminal to fill this
  238.  buffer by reading the com port and delivering the input to Kermit via a call
  239.  to the state switcher wart().
  240.  
  241.  Kermit and the Terminal's Main WinProc
  242.  
  243.  Of course, the terminal's main WinProc is also affected by the addition of
  244.  Kermit support. When the main terminal window is created, a call is made to
  245.  Kermit at WM_CREATE time to pass along information such as the window
  246.  handle, buffer information, and the location of the communications port
  247.  identifier. Except for the handle, this information changes dynamically, so
  248.  pointer variables are needed. During the initialization, WIN.INI is also
  249.  read under the subhead [Kermit] to provide user settings pertaining to the
  250.  protocol and inbound and outbound packets. The Kermit menu is also added to
  251.  the end of the terminal's main menu. Finally, if the port to be used is not
  252.  yet known, the Kermit menu should be initially disabled.
  253.  
  254.  In the event either Windows or the program is closed, any global resources
  255.  used by Kermit must be freed. If termination is attempted in the middle of a
  256.  transfer, the user must have a chance to change his or her mind. In the
  257.  Kermit exit code, a message box appears if a transfer is still in progress.
  258.  If the reply is to shut down (IDYES), an error packet is sent to the remote
  259.  Kermit, all files are closed (incompletely received files are deleted if
  260.  this option has been set), and the response to WM_CLOSE or
  261.  WM_QUERYENDSESSION proceeds as usual. Otherwise, the session continues as if
  262.  nothing happened.
  263.  
  264.  The Kermit menu allows the user to send a group of files (see Figure 8),
  265.  receive a group of files, cancel a transfer in one of several ways, and
  266.  select default values for several protocol and packet parameters (see
  267.  Figures 9 and 10). The Kermit menu processor returns TRUE if a menu item was
  268.  recognized and handled. Otherwise it returns FALSE; this is a signal for the
  269.  terminal to execute its own handler for WM_COMMAND.
  270.  
  271.  When Kermit is in action, any child windows on the screen are hidden and a
  272.  modeless dialog box is posted to show the state of the transfer (see Figure
  273.  11). Of course, the transfer continues even if the terminal program is
  274.  minimized. If the terminal program draws its own icon rather than using a
  275.  class icon, Kermit will show the number of packets exchanged like an
  276.  odometer (with rollover every 10,000 packets) in the icon window. This lets
  277.  the user monitor progress even when he or she is performing other tasks and
  278.  the terminal is running in the background (see Figure 12). This display is
  279.  managed in response to the WM_PAINT message.
  280.  
  281.  Finally, as already noted, Kermit calls the state machine in response to a
  282.  programmer-defined message. You have already seen how such a message is
  283.  generated by PeekMessage.
  284.  
  285.  Conclusion
  286.  
  287.  By breaking up a long process into naturally occurring shorter ones, it is
  288.  possible to preserve the cooperative multitasking feature of Windows and
  289.  still get the job done. Although Kermit has been used to illustrate specific
  290.  techniques, the principles presented here can be applied to other
  291.  communication protocols, as well as complex, time-consuming tasks that can
  292.  be described with a finite state machine approach.
  293.  
  294.  1  For ease of reading, "Windows" refers to the Microsoft Windows graphical
  295.  environment. "Windows" refers to this Microsoft product only and is not
  296.  intended to refer to such products generally.
  297.  
  298.  2  As used herein, "DOS" refers to the MS-DOS and PC-DOS operating systems.
  299.  
  300.  The Kermit File Transfer Protocol
  301.  
  302.  Like its famous puppet namesake, the Kermit file transfer protocol is
  303.  internationally known. Since its introduction in 1980, Kermit has become a
  304.  mainstay of academic and business computing, supported on machines from the
  305.  largest mainframes to the tiniest microcomputers. Operating systems
  306.  supporting this protocol include VM/CMS, DOS2, OS/2, CP/M, UNIX, and even
  307.  the USCD P-System. Kermit has been written in Algol, Pascal, C, FORTRAN,
  308.  BASIC, BCPL, CROSS, Compass, FORTH, PAL-8, MUMPS, BLISS, PL/1, Ratfor, PL/M,
  309.  Modula-2, LISP, and quite a few assembly languages. Kermit's importance is
  310.  rivaled only by the XMODEM protocol. There is even an IBM 370 version of
  311.  Kermit in Russian.
  312.  
  313.  Kermit was written to answer the need for a reliable means of transferring
  314.  files from micros to mainframes. Simply uploading and downloading files is
  315.  often unreliable. Unusual file formats, noisy lines, or busy mainframes
  316.  expecting input at human typing speed frequently result in a trashed
  317.  transfer. To address this problem, Frank da Cruz and his associates at
  318.  Columbia University developed a protocol that supported operations between
  319.  DECSYSTEM-20 or IBM 370 systems and microcomputers running CP/M and DOS.
  320.  These early programs were made available to users along with the source
  321.  code. Today, Kermit remains free and is widely available at no more than the
  322.  cost of distribution. This policy has not only broadened the support base
  323.  for Kermit, it has also contributed to its astounding acceptance by the
  324.  computing community.
  325.  
  326.  At first, Kermit was only capable of transferring text files in packets
  327.  containing at most 94 characters, using a simple 1-byte checksum. Since
  328.  then, Kermit has been extended to include binary transfers over 7- and 8-bit
  329.  wide paths, run-length encoding, CRC checksums, long packets, transfers over
  330.  networks, server modes, and more recently, sliding windows. Today, you can
  331.  not only send your file, you can preserve its attributes including its date
  332.  and time of creation. Kermit can even translate between varying character
  333.  sets such as ASCII, ISO Latin I, ISO Latin/Cyrillic, ISO Latin/Hebrew, and
  334.  Japanese JIS X 0208. Kermit has special features for users with visual,
  335.  hearing, or motor impairments, including speech synthesis, a simple
  336.  command-line interface, and special program documentation.
  337.  
  338.  Most Kermit programs offer sophisticated terminal emulation as well as file
  339.  transfer. For example, Version 3.0 for DOS, written by Joe Doupnik of Utah
  340.  State University, emulates DEC VT320 terminals and supports Tektronix
  341.  graphics. Other features include screen rollback, Windows compatibility, a
  342.  visual bell for deaf users, right-to-left screen display for Hebrew and
  343.  Arabic, and a complete scripting language. Kermit rivals commercially
  344.  available products, but costs at most $20.00.
  345.  
  346.  Building a Finite State Machine with Wart
  347.  
  348.  
  349.  
  350.  Wart, written by Jeff Damens of Columbia University, is a tool that builds
  351.  state table switchers. Wart contains a small subset of the UNIX lexical
  352.  analyzer generator, lex, and may be freely distributed (lex, which means
  353.  "word" in Latin, translates to wort in German, which sounds like wart on a
  354.  frog).
  355.  
  356.  Wart accepts as input a C program in the following format.
  357.  
  358.  
  359.    lines to be copied | %state <state names...>
  360.    %%
  361.    <state> | <state,state,...> CHAR  { actions }
  362.  
  363.          .
  364.          .
  365.          .
  366.  
  367.    %%
  368.  
  369.  
  370.  
  371.  The %state directive declares the program's states. The section enclosed
  372.  between the %% delimiters is the state table. A typical entry has the form
  373.  
  374.  
  375.    <state>X {action}
  376.  
  377.  
  378.  
  379.  which is read as
  380.  
  381.  "if in state <state> with input X, perform {action}".
  382.  
  383.  The optional <state> field names the states the program must be in to
  384.  perform the related action. If no state is specified, the action is carried
  385.  out regardless of the current state. If more than one state is specified,
  386.  the action is performed in any of the listed states. Multiple states are
  387.  separated by commas.
  388.  
  389.  The required input field consists of a single literal character. In a given
  390.  state, if the input is the specified character, the associated action is
  391.  performed. The character . matches any input character. No pattern matching
  392.  or range notation is provided. The input character itself is obtained from a
  393.  function called input(), which the user must define. Input should return an
  394.  alphanumeric character or one of the following characters.
  395.  
  396.          %    -    _    $    @    .
  397.  
  398.  The action statement is a series of zero or more C statements enclosed in
  399.  curly braces. Wart also provides the BEGIN macro, which is defined as state
  400.  = , as it is in lex. Wart is invoked at the command line as follows:
  401.  
  402.  
  403.  wart file.w file.c
  404.  
  405.  
  406.  
  407.  In this example, Wart reads FILE.W and produces FILE.C, which can then be
  408.  compiled as an ordinary C program. Wart's output contains the function
  409.  called wart(), whose form depends on the state declarations and the state
  410.  transition table. Wart loops through calls to input() and uses the result to
  411.  index into a case statement created from the state table.
  412.  
  413.  The following program demonstrates some of the capabilities and limitations
  414.  of Wart. BINTODEC.W accepts a binary number from the command line, preceded
  415.  by an optional minus sign and possibly containing a fractional part, and
  416.  prints the decimal equivalent.
  417.  
  418.  
  419.  BINTODEC.W
  420.  #include <stdio.h>
  421.  int state, s = 1, m = 0, d;
  422.  float f;
  423.  char *b;
  424.  
  425.  /* Declare wart states */
  426.  %states sign mantissa fraction
  427.  
  428.  /* Begin state table */
  429.  %%
  430.  <sign>-  { s = -1; BEGIN mantissa; }  /* Look for sign */
  431.  <sign>0  { m = 0;  BEGIN mantissa; }  /*Got digit,start mantissa */
  432.  <sign>1      { m = 1;  BEGIN mantissa; }
  433.  <sign>.      { fatal("bad input"); }      /* Detect bad format */
  434.  <mantissa>0  { m *= 2; }                /* Accumulate mantissa */
  435.  <mantissa>1  { m = 2 * m + 1; }
  436.  <mantissa>$  { printf("%d\n", s * m); return; }
  437.  <mantissa>.  { f=0.0; d = 1; BEGIN fraction; } /* Start fraction */
  438.  <fraction>0  { d *= 2; }                  /* Accumulate fraction */
  439.  <fraction>1  { d *= 2; f += 1.0 / d; }
  440.  <fraction>$  { printf("%f\n", s * (m + f) ); return; }
  441.  <fraction>.  { fatal("bad input\n"); }
  442.  %%
  443.  
  444.  input(void) {       /* Define input() function */
  445.      int x;
  446.      return(((x = *b++) = = '\0') ? '$' : x );
  447.  }
  448.  
  449.  fatal(char *s) {    /* Error exit */
  450.  
  451.      fprintf(stderr,"fatal - %s\n",s);
  452.      exit(1);
  453.  }
  454.  
  455.  main(int argc,char **argv) {    /* Main program */
  456.      if (argc < 2) {
  457.          fprintf(stderr, "Not enough input\n");
  458.          exit(1);
  459.      }
  460.      b = *++argv;
  461.      state = sign;           /* Initialize state */
  462.      wart();                 /* Invoke state switcher */
  463.      exit(0);                /* Done */
  464.  }
  465.  
  466.  
  467.  
  468.  The following code is generated by processing BINTODEC.W with WART.EXE:
  469.  
  470.  BINTODEC.C
  471.  
  472.  /* WARNING --This C source program generated by Wart preprocessor.*/
  473.  /* Don't edit this C file; edit the Wart-format .W file instead, */
  474.  /* and then run it through Wart to produce a new C source file.  */
  475.  
  476.  /* Wart Version Info: */
  477.  char *wartv = "Wart Version 1A(006) Jan 1989";
  478.  
  479.  #include <stdio.h>
  480.  
  481.  int state, s = 1, m = 0, d;
  482.  float f;
  483.  char *b;
  484.  
  485.  /* Declare wart states */
  486.  #define sign 1
  487.  #define mantissa 2
  488.  #define fraction 3
  489.  
  490.  /* Begin state table */
  491.  
  492.  
  493.  #define BEGIN state =
  494.  
  495.  int state = 0;
  496.  
  497.  wart()
  498.  {
  499.      int c,actno;
  500.      extern short tbl[];
  501.      while (1) {
  502.   c = input();
  503.   if ((actno = tbl[c + state*128]) != -1)
  504.       switch(actno) {
  505.  case 1:
  506.      { s = -1; BEGIN mantissa; }
  507.      break;
  508.  case 2:
  509.      { m = 0;  BEGIN mantissa; }
  510.      break;
  511.  case 3:
  512.      { m = 1;  BEGIN mantissa; }
  513.      break;
  514.  case 4:
  515.      { fatal("bad input"); }
  516.      break;
  517.  case 5:
  518.      { m *= 2; }
  519.      break;
  520.  case 6:
  521.      { m = 2 * m + 1; }
  522.      break;
  523.  case 7:
  524.      { printf("%d\n", s * m); return; }
  525.      break;
  526.  case 8:
  527.      { f = 0.0; d = 1; BEGIN fraction; }
  528.      break;
  529.  case 9:
  530.      { d *= 2; }
  531.      break;
  532.  case 10:
  533.      { d *= 2; f += 1.0 / d; }
  534.      break;
  535.  case 11:
  536.      { printf("%f\n", s * (m + f) ); return; }
  537.      break;
  538.  case 12:
  539.      { fatal("bad input\n"); }
  540.      break;
  541.       }
  542.      }
  543.  }
  544.  
  545.  short tbl[] = {
  546.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  547.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  548.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  549.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  550.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  551.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  552.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  553.  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  554.  -1,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  555.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  556.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  1,  4,  4,
  557.   2,  3,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  558.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  559.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  560.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  561.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  562.  -1,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  563.   8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  564.   8,  8,  8,  8,  7,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  565.   5,  6,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  566.   8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  567.   8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  568.   8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  569.   8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
  570.   0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  571.  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  572.  12, 12, 12, 12, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  573.   9, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  574.  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  575.  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  576.  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  577.  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
  578.  };
  579.  
  580.  input(void) {       /* Define input() function */
  581.      int x;
  582.      return(((x = *b++) = = '\0') ? '$' : x );
  583.  }
  584.  
  585.  fatal(char *s) {                    /* Error exit */
  586.      fprintf(stderr,"fatal - %s\n",s);
  587.      exit(1);
  588.  }
  589.  
  590.  main(int argc,char **argv) {    /* Main program */
  591.      if (argc < 2) {
  592.          fprintf(stderr, "Not enough input\n");
  593.          exit(1);
  594.      }
  595.      b = *++argv;
  596.      state = sign;           /* Initialize state */
  597.      wart();                 /* Invoke state switcher */
  598.      exit(0);                /* Done */
  599.  }
  600.  
  601.  
  602.  
  603.  
  604.  Creating a Network Service Using the Client-Server Model and LAN Manager 2.0
  605.  
  606.  Brendan W. Dixon
  607.  
  608.  As the PC industry has matured, concepts and techniques pioneered on larger
  609.  systems have been adapted for PC users. Many of these, such as preemptive
  610.  multitasking, virtual memory and paging, are little changed on PCs. Others
  611.  have been combined with features unique to PCs, producing new uses for
  612.  computers. For example, when the graphical capability of the PC was combined
  613.  with the large-system approach to document preparation, a whole new
  614.  industry, desktop publishing, was created. The client-server model combines
  615.  the large-system concept of centralized resources with the PC's peer-to-peer
  616.  approach to networking, and lays the groundwork for distributed processing.
  617.  
  618.  The client-server model is an application design approach that distributes
  619.  resources and processing between systems in a network. Unlike the standard
  620.  network model, where each client participates in resource management, the
  621.  client-server model divides responsibilities and intelligence. Servers
  622.  contain all of the intelligence necessary to own and manage the resource
  623.  (for example, a database server, as in Figure 1). Clients contain only
  624.  nominal information about the resource (that it is a database, for instance)
  625.  and do not assist in its management; they pass requests to the server, which
  626.  the server decides to accept or reject. Conversely, servers are unaware of
  627.  the client's intentions. A server only responds to requests for the resource
  628.  it is managing (say, a database transaction). Centralizing resource
  629.  management onto the server simplifies error handling and reduces the network
  630.  load. Removing resource management tasks from the clients reduces processing
  631.  requirements on workstation hardware and allows them to focus on their
  632.  primary responsibilities such as interfacing with the end user.
  633.  
  634.  Microsoft LAN Manager is designed to support client-server programming.
  635.  While LAN Manager provides all of the facilities of standard PC networks
  636.  (that is, file and printer sharing), it also provides a platform that
  637.  simplifies the construction of client-server-based applications and network
  638.  communication.
  639.  
  640.  This article examines the building of a LAN Manager service. Creating and
  641.  managing a group of remote named pipes will be discussed in a future
  642.  article, as well as the construction of a LAN Manager client under the
  643.  Microsoft Windows and OS/2 Presentation Manager graphical environments.
  644.  
  645.  The LAN Manager Application Programming Interface (API) gives the programmer
  646.  a robust set of function calls to communicate with LAN Manager and issue
  647.  network requests. Almost any network task that can be performed from the
  648.  command line or by using LAN Manager's full-screen interface can also be
  649.  done through in-line code. There are nearly 130 API calls (see Figure 2),
  650.  and many take modal parameters that can be used to tune the function.
  651.  
  652.  Most important for client-server programmers is LAN Manager's support of
  653.  remote named pipes. Named pipes are one-way or bidirectional facilities that
  654.  allow processes to communicate. They offer guaranteed delivery and built-in
  655.  buffering, simplified error detection, and access security.
  656.  
  657.  Another feature of LAN Manager is nonconnection oriented messaging through
  658.  mailslots. Mailslots are useful for peer-to-peer communication, message
  659.  broadcasting, and client-server communication that does not require all the
  660.  facilities of a named pipe. Mailslots are either first-class (with
  661.  guaranteed delivery) or second-class (without guaranteed delivery). Remote
  662.  first-class mailslots, which can receive messages from remote workstations,
  663.  must be created on a LAN Manager server. But any workstation may send or
  664.  receive remote messages on a second-class mailslot.
  665.  
  666.  Named pipes and mailslots enable the programmer to build applications that
  667.  utilize the network without being network-aware. LAN Manager also makes it
  668.  easy for the network administrator to control access to named pipes and
  669.  mailslots, and therefore the network. These facilities lead to easily built,
  670.  easily maintained, and easily controlled client-server applications.
  671.  
  672.  Sample LAN Manager Service
  673.  
  674.  In order to provide a consistent method of control for the network
  675.  administrator, LAN Manager was designed as an extensible system, implemented
  676.  as a group of services. A LAN Manager service is a detached OS/2 process
  677.  (that is, no console I/O) running as an extension of LAN Manager. Such a
  678.  service can be controlled (that is, started, paused, continued, or stopped)
  679.  through LAN Manager. Developing a LAN Manager service requires only that the
  680.  service be listed in the LANMAN.INI file, that it register itself with LAN
  681.  Manager as part of its start-up processing, and that it respond to LAN
  682.  Manager requests, which are sent to the service through the OS/2 signal
  683.  mechanism. Additionally, a LAN Manager service may take tuning parameters;
  684.  these are also normally specified in the LANMAN.INI file.
  685.  
  686.  In the client-server model, the server makes resources available to the
  687.  client and manages those resources for the client. One server resource,
  688.  normally unavailable to clients, is the ownership of remote named pipes.
  689.  While a client may open and communicate on a remote named pipe, it cannot
  690.  create the pipe; remote named pipes are a resource provided by a LAN Manager
  691.  server and must be created on the server.
  692.  
  693.  A sample LAN Manager service designed for this article, PBX, can serve as
  694.  the foundation for real-world client-server applications. PBX gives clients
  695.  the ability to establish peer-to-peer communications using named pipes
  696.  through a transparent routing mechanism. Clients register with PBX and
  697.  request connections with other clients. Once a connection is established
  698.  between two clients, PBX maintains the image of a peer-to-peer named pipe
  699.  connection for each client until the connection is broken. PBX creates at
  700.  least three threads in addition to the main thread created when PBX is given
  701.  control. Each thread is named after its C routine (see Figure 3). This
  702.  article examines the main thread, which initializes PBX and communicates
  703.  with LAN Manager. A future article will examine the remaining threads, which
  704.  create and maintain the virtual named pipe connections (see Figure 4 for the
  705.  source code files used to build PBXSRV.EXE).
  706.  
  707.  To build a LAN Manager application, you will need either the Microsoft LAN
  708.  Manager Programmer's Toolkit or the Microsoft LAN Manager Developer's
  709.  Toolkit. These toolkits include the C header files and libraries utilized
  710.  in a LAN Manager application.
  711.  
  712.  To support a LAN Manager API category in your program, you add one or more
  713.  #define statements and a #include for LAN.H in your C source file. Figure 2
  714.  lists the C preprocessor constants for each LAN Manager API category. For
  715.  example, to include support for mailslots, you would add the following to
  716.  your program file:
  717.  
  718.  
  719.  #define INCL_NETMAILSLOT // Include mailslots
  720.  #define INCL_NETERRORS   // Include error constants
  721.  #include <lan.h>
  722.  
  723.  
  724.  As for linking your application, most LAN Manager applications need to link
  725.  only with LAN.LIB, so you simply add this library to those you would
  726.  normally use (see Figure 5).
  727.  
  728.  To build a LAN Manager service you will need to include support for the
  729.  service APIs by including a #define statement for INCL_NETSERVICE before you
  730.  #include LAN.H. Your program must also contain an OS/2 signal handler and
  731.  use the service APIs to maintain a simple communication protocol with LAN
  732.  Manager. These points will be covered in more detail further on.
  733.  
  734.  Installing a LAN Manager Service
  735.  
  736.  A service is started from the command line using the LAN Manager NET command
  737.  or using the LAN Manager full-screen interface. The PBX service could be
  738.  started from an OS/2 command prompt by typing:
  739.  
  740.  
  741.  NET START PBX
  742.  
  743.  
  744.  Services can also be started and stopped automatically when the LAN Manager
  745.  Workstation or Server service starts or stops by adding the service name to
  746.  either the wrkservices or srvservices line in LANMAN.INI (see the Microsoft
  747.  LAN Manager Administrator's Reference for details on LANMAN.INI).
  748.  
  749.  When requested to start a service, LAN Manager scans the services section of
  750.  LANMAN.INI for an entry describing where to find the EXE to execute. Each
  751.  entry under the services section names a service and supplies the location
  752.  and name of the EXE file to be executed to start the service.
  753.  
  754.  [services]
  755.  
  756.      .
  757.      .
  758.      .
  759.   service_name = service_exe_path
  760.  
  761.  The service_name is the name the service is known by to LAN Manager and the
  762.  user; with our sample, it's PBX. The service_exe_path describes the path of
  763.  the EXE file to execute. If the service EXE is in the LAN Manager directory,
  764.  the pathname can be relative. This is the case for the services shipped with
  765.  LAN Manager. If the EXE is outside the LAN Manager directory, the full
  766.  pathname and drive must be supplied.
  767.  
  768.  Optionally, a service may take configuration parameters. These are also
  769.  specified in LANMAN.INI (though the user may override them when starting the
  770.  service),
  771.  
  772.  and should be placed under a section whose heading name is the same as the
  773.  service. PBX has five configuration parameters; they would be listed in
  774.  LANMAN.INI as shown in Figure 6.
  775.  
  776.  Start-up of a LAN Manager Service
  777.  
  778.  A LAN Manager service receives control at the C main function like any other
  779.  C program. Immediately upon receiving control, a service obtains its own
  780.  process identifier (PID) and registers with LAN Manager by calling the
  781.  NetServiceStatus API:
  782.  
  783.  
  784.   PIDINFO    pidInfo; // OS/2 PID info
  785.   struct service_status ssStatus; // LM Service info
  786.  
  787.   // Obtain the process ID of this process
  788.   usRetCode = DosGetPID(&pidInfo);
  789.  
  790.      .
  791.      .
  792.      .
  793.  
  794.   // Inform LAN Manager that this service is in the
  795.   // process of installation
  796.   // Because the signal handler is not yet installed,
  797.   // the service cannot be uninstalled or paused
  798.   ssStatus.svcs_pid = pidInfo.pid;
  799.   ssStatus.svcs_status = SERVICE_INSTALL_PENDING |
  800.                          SERVICE_NOT_UNINSTALLABLE |
  801.                          SERVICE_NOT_PAUSABLE;
  802.   ssStatus.svcs_code=SERVICE_CCP_CODE (PBXINSTALLTIME,
  803.                                        0);
  804.   usRetCode = NetServiceStatus((char _far *)&ssStatus,
  805.                                sizeof(ssStatus));
  806.  
  807.  
  808.  The NetServiceStatus API call takes as its primary argument a pointer to a
  809.  buffer that must contain a service_status structure (see Figure 7). LAN
  810.  Manager needs the PID of the service to pass control requests to the
  811.  service. When a request is made to control a service (for example, NET PAUSE
  812.  PBX), LAN Manager signals the service through the OS/2 signal mechanism,
  813.  passing a request code in the first byte of the signal argument.
  814.  
  815.  NetServiceStatus is also used by the service to pass status or error
  816.  information to LAN Manager. For example, if your service takes more than a
  817.  few seconds to install or respond to any LAN Manager request, it should
  818.  periodically inform LAN Manager that the request is still pending by calling
  819.  NetServiceStatus. Status and error information is passed by setting the
  820.  svcs_status and svcs_code fields in the service_status structure. Your
  821.  service should set the svcs_status field to the bitwise OR of the
  822.  appropriate values (see Figure 8). The service uses the SERVICE_CCP_CODE
  823.  macro to set the svcs_code field during installation and while responding to
  824.  requests from LAN Manager. The first macro argument is the approximate time
  825.  the service will take to install or complete the request in tenths of a
  826.  second. The second argument is a checkpoint counter that should be
  827.  incremented each time the service informs LAN Manager a request is still
  828.  pending.
  829.  
  830.  If an error occurs and your service cannot install, set the svcs_status
  831.  field to SERVICE_UNINSTALLED and use the SERVICE_UIC_CODE macro to set the
  832.  svcs_code with the uninstall information code (UIC). Some UICs allow an
  833.  additional text string to be passed in the svcs_text field. The first
  834.  argument to the macro is the code and the second is an optional modifier
  835.  (see Figure 9). When the service is uninstalled, use the SERVICE_UIC_CODE
  836.  macro to supply LAN Manager with a reason code. For instance,
  837.  SERVICE_UIC_NORMAL is the proper UIC during a normal uninstall.
  838.  
  839.  After registering with LAN Manager, the service should next establish a
  840.  signal handler for communication with LAN Manager. As mentioned, LAN Manager
  841.  communicates with a service by sending OS/2 signals. It does this using the
  842.  OS/2 DosFlagProcess API. OS/2 defines three process flags (that is,
  843.  signals): PFLG_A, PFLG_B, and PFLG_C. LAN Manager sends requests to a
  844.  service using the process flag A (PFLG_A) signal. LAN Manager also defines a
  845.  special constant, SERVICE_RCV_SIG_FLAG, which equates to PFLG_A, that can be
  846.  used when registering a signal handler to inform OS/2 that the service
  847.  should receive signals from LAN Manager. After establishing your signal
  848.  handler, inform OS/2 that your handler will ignore CTRL-C, break, and kill
  849.  process signals. You want your service to shut down only if requested to by
  850.  LAN Manager. Additionally, unless they are used by your service for
  851.  communication, process flags B (PFLG_B) and C (PFLG_C) should be regarded as
  852.  errors. The InstallSignals function in SIGNALS.C (see Figure 4) demonstrates
  853.  the proper technique for establishing a signal handler to communicate with
  854.  LAN Manager. Once your signal handler is in place, your service should call
  855.  NetServiceStatus again and update LAN Manager with its new status. For
  856.  instance, once the signal handler is installed, PBX can be uninstalled,
  857.  though it cannot be paused until installation is complete.
  858.  
  859.  Communicating with LAN Manager
  860.  
  861.  LAN Manager control requests are passed to your signal handler in the low
  862.  byte of the signal argument, the first argument passed to an OS/2 signal
  863.  handler. Again, your handler should respond to these requests quickly or
  864.  give LAN Manager status hints as your service is responding. Currently, LAN
  865.  Manager passes one of four arguments (see Figure 10). Values 0 through
  866.  127 are reserved, leaving 128 through 255 available for your internal
  867.  communication. The simplest way to build your signal handler is to use a
  868.  switch statement with a case for each possible argument. The PBX signal
  869.  handler, SignalHandler in SIGNALS.C, follows the format shown in Figure 11.
  870.  
  871.  The requests SERVICE_CTRL_UNINSTALL and SERVICE_CTRL_INTERROGATE must be
  872.  supported by your service. When a SERVICE_CTRL_UNINSTALL is received, your
  873.  service should take whatever steps are appropriate for clean up and exit.
  874.  Prior to exiting, call NetServiceStatus one last time with the appropriate
  875.  UIC code to inform LAN Manager that your service has successfully
  876.  uninstalled. The ExitHandler routine in SIGNALS.C handles all exit requests,
  877.  error or otherwise, for PBX.
  878.  
  879.  In response to a SERVICE_CTRL_INTERROGATE request, your service should call
  880.  NetServiceStatus with the current state of the service. PBX maintains a
  881.  global service_status structure that always reflects the current
  882.  state of PBX and is returned in response to SERVICE_CTRL_INTERROGATE
  883.  requests.
  884.  
  885.  If you allow pausing of your service (as PBX does), you need to process
  886.  SERVICE_CTRL_PAUSE and SERVICE_CTRL_CONTINUE requests. A simple and
  887.  efficient method is to use OS/2 semaphores to control child threads. If the
  888.  semaphore is set, the child thread waits until it is cleared.
  889.  
  890.  Initializing a LAN Manager Service
  891.  
  892.  Once your service is registered with LAN Manager and your signal handler is
  893.  in place, you can proceed with initialization. All services should redirect
  894.  file handles zero, one, and two (corresponding to standard input, standard
  895.  output, and standard error) to the NUL device. The technique used by the
  896.  RedirectFiles routine in PBXSRV.C (see Figure 4) is the easiest: it opens
  897.  the NUL device for I/O and redirects handles zero through two by calling
  898.  DosDupHandle.
  899.  
  900.  After disabling the standard file handles, your service can perform any
  901.  specific initialization it has. At this point, most services process the
  902.  configurable parameters that were specified either in LANMAN.INI or by the
  903.  user when the service was started. LAN Manager passes these parameters to
  904.  your service as a standard OS/2 argument string. Values explicitly specified
  905.  by the user when the service was started are passed first, followed by any
  906.  values from your service's section in LANMAN.INI that were not overridden
  907.  when starting the service. These values arrive as the standard C argc-argv
  908.  pair. For example, if PBX was started from the command line as below,
  909.  
  910.  
  911.  NET START PBX /Lines:200 /Connsperthread:10
  912.  
  913.  
  914.  and LANMAN.INI contained the values listed in Figure 6, PBX would receive
  915.  the following in argv:
  916.  
  917.  
  918.   argv[0] = Pathname of PBX .EXE file
  919.   argv[1] = "Lines=200"
  920.   argv[2] = "Connsperthread=10"
  921.   argv[3] = "LINEBUFSIZE=1024"
  922.   argv[4] = "OPENLINES=20"
  923.   argv[5] = "AUDITING=NO"
  924.  
  925.  
  926.  As you can see, the keywords are not in any specific order or case. LAN
  927.  Manager preprocesses the keywords to remove blanks and converts the
  928.  separating colon to an equal-sign (the user may specify either a colon or an
  929.  equal-sign when entering the keyword and its value). Any text following the
  930.  equal-sign or colon is treated as the keyword value, including comments, and
  931.  is passed to your service. If while overriding a keyword in LANMAN.INI the
  932.  user enters only a part of the keyword name, LAN Manager still passes the
  933.  full keyword from LANMAN.INI (if it exists). Since explicitly specified
  934.  keywords always come first, you may want to ignore second and subsequent
  935.  occurrences of a keyword; this allows the user to override values in
  936.  LANMAN.INI using short-cuts when starting the service.
  937.  
  938.  Making a Service Known
  939.  
  940.  Some services, such as PBX, offer a resource that will be used
  941.  intermittently by clients. In such situations, it benefits the client
  942.  application if the service can be automatically detected on the network. PBX
  943.  informs clients of its existence by waiting for broadcasts on a mailslot and
  944.  then responding with a message containing the name of the computer on which
  945.  it is executing.
  946.  
  947.  A mailslot name looks like a pathname without a drive specification whose
  948.  root directory is named MAILSLOT. Remote mailslot names are preceded with
  949.  either the name of an individual computer (both first-class and second-class
  950.  mailslots) or a logical group of computers called a domain, which is used
  951.  with second-class mailslots only. A second class message sent to a mailslot
  952.  with the following name
  953.  
  954.  
  955.  \\*\MAILSLOT\mailslotname
  956.  
  957.  
  958.  sends the message to all machines in the same domain as the sending LAN
  959.  Manager workstation. It is recommended that the first qualifier in a
  960.  mailslot name, after the keyword MAILSLOT, be a corporate or product
  961.  identifier to avoid conflicts with other applications. PBX creates a
  962.  mailslot with the following name, using MSJ as the qualifier:
  963.  
  964.  
  965.  \mailslot\msj\pbx
  966.  
  967.  
  968.  When a message arrives on its mailslot, PBX responds by sending the name of
  969.  the computer on which it is executing prefixed with two backslashes, as in
  970.  the start of a remote name. As a message from the client, PBX expects the
  971.  computer name on which the client application is executing prefixed with two
  972.  backslashes. PBX calls the NetWkstaGetInfo function to get the computer name
  973.  on which it is executing. The call is issued twice, the first time with a
  974.  zero-length buffer and the second time with the actual buffer size needed.
  975.  Any LAN Manager call that returns a structure containing pointers to strings
  976.  or other data (instead of a fixed-length array) returns the structure and
  977.  the data addressed by the structure in the supplied buffer. Since the buffer
  978.  size cannot be statically predetermined, you should make the LAN Manager API
  979.  call twice. On the first call your service would pass a zero-length buffer.
  980.  LAN Manager will return NERR_BufTooSmall for a return code and the actual
  981.  number of bytes needed in the appropriate argument (the last argument on the
  982.  NetWkstaGetInfo call). Next, you allocate a buffer of the required size and
  983.  make the call again to retrieve the data.
  984.  
  985.  If it is necessary to ensure that only a single instance of your service
  986.  exists in a particular LAN Manager domain, a similar approach may be used.
  987.  Your service would create a mailslot and wait for messages to arrive, as PBX
  988.  does; if a message is received, your service would send an appropriate
  989.  response. To determine if an instance of your service is already executing,
  990.  your service should send a broadcast message on the mailslot while it is
  991.  initializing, and briefly wait for a response. If no response is received,
  992.  your service could assume that it is running alone.
  993.  
  994.  Multiple Threads in a LAN Manager Service
  995.  
  996.  When writing a service it's best to use a multithreaded design. Since LAN
  997.  Manager communicates with a service through the OS/2 signal mechanism, your
  998.  service's main thread will be used to process LAN Manager signals. With
  999.  simpler services, it won't be a problem if one thread is used for all
  1000.  processing. But in more complex programs, the sudden interruption of
  1001.  processing may leave your service in an unstable state. After initialization
  1002.  is completed, your main application thread should sleep. This is done most
  1003.  efficiently by waiting on a dummy OS/2 semaphore.
  1004.  
  1005.  When its initialization is completed, PBX uses its main thread for
  1006.  processing signals and responding to broadcasts arriving on its mailslot.
  1007.  
  1008.  
  1009.  // Run until PBX is shutdown
  1010.    do {
  1011.  // Wait until a signal is received or a message
  1012.  // arrives on the mailslot (which means a client is /// looking for the PBX)
  1013.  
  1014.      usRetCode = DosReadMailslot(
  1015.                pbPBXMem->hMailslot,   // Handle
  1016.                pbPBXMem->pbMSlotBuf,  // Buffer
  1017.                &usByteCnt,            // Bytes read
  1018.                &usNextCnt,            // Next msg size
  1019.                &usNextPriority,       // Next msg
  1020.                                          priority
  1021.                MAILSLOT_NO_TIMEOUT);  // Wait forever
  1022.  
  1023.    // If control has been returned because a client
  1024.    // sent a message,return to the client the computer
  1025.    // name of the machine on which PBX is executing
  1026.      if (usRetCode == NERR_Success &&
  1027.          usByteCnt != 0             ) {
  1028.        strcat(pbPBXMem->pbMSlotBuf, ANNOUNCELINE);
  1029.        usRetCode = DosWriteMailslot(
  1030.                  pbPBXMem->pbMSlotBuf,    // Mailslot
  1031.                  pbPBXMem->pszPBXMsg,     // Message
  1032.                  pbPBXMem->usPBXMsgSize,  // Message
  1033.                                              size
  1034.                  9,                       // Priority
  1035.                  2,                       // 2nd class
  1036.                  0L);                     // No wait
  1037.      }
  1038.  
  1039.    } while (usRetCode == ERROR_INTERRUPT   ||
  1040.             usRetCode == ERROR_SEM_TIMEOUT  );
  1041.  
  1042.    // Control never arrives here; the ExitHandler
  1043.    // always exits PBX
  1044.    return;
  1045.  }
  1046.  
  1047.  
  1048.  
  1049.  Logging Events with LAN Manager
  1050.  
  1051.  To assist the network administrator, your service should log significant
  1052.  events with LAN Manager. LAN Manager provides API support for two event
  1053.  logs, an error event log and a non-error (or audit) log. At the very least,
  1054.  errors that cause your service to shut down should be reported to LAN
  1055.  Manager in the svcs_code field of the service_status structure (as noted
  1056.  above). However, for these and other errors, your service should also log
  1057.  the event with LAN Manager.
  1058.  
  1059.  Whenever a significant error is encountered, PBX writes a message to the LAN
  1060.  Manager error log using NetErrorLogWrite. The second argument passed to this
  1061.  function is the error number. There are more than 120 error numbers. Most
  1062.  are specific to services shipped with LAN Manager, some may be used by your
  1063.  service, and one, NELOG_OEM_Code, was designed for third-party services.
  1064.  Your service can pass NULL-separated substitution strings for the message
  1065.  and the number of substitution strings as the sixth and seventh parameters
  1066.  to NetErrorLogWrite. The NELOG_OEM_Code error message is defined as nine
  1067.  substitutable strings (with no predefined text). It is recommended that the
  1068.  first four strings be the company name, service name, error severity (such
  1069.  as error versus warning), and subidentifier code. The remaining five strings
  1070.  should contain additional data or be initialized to empty.
  1071.  
  1072.  PBX writes all of its error messages to the error log via the ErrorRpt
  1073.  routine in PERROR.C (see Figure 4) using the NELOG_OEM_Code error code. In
  1074.  addition to the recommended data, PBX supplies the filename and line number
  1075.  of the place where the error occurred as part of the message. While this may
  1076.  be unnecessary after you ship your service, it is extremely helpful during
  1077.  debugging when similar errors may occur along different code paths.
  1078.  
  1079.  In addition to logging errors, PBX logs significant, non-error events in the
  1080.  LAN Manager audit log by calling the NetAuditWrite function at the
  1081.  appropriate times. Audit logging is enabled/disabled in PBX by the AUDITING
  1082.  keyword, which defaults to YES. Each record in the LAN Manager audit log is
  1083.  variable length and contains a fixed header, followed by a variable amount
  1084.  of event-specific data, and a 2-byte field. The fixed header contains the
  1085.  total length of the record (the length value at the end of the record is
  1086.  redundant), the type of the record, and the time the record was written.
  1087.  Your service does not build either the fixed header nor the length field at
  1088.  the end of the record. You supply only the type of the audit event and the
  1089.  event-specific data; everything else is built and supplied by the
  1090.  NetAuditWrite function. Microsoft reserves audit event types 0 through
  1091.  32,767; audit event types in the range 32,768 through 65,535 may be used by
  1092.  third-party applications.
  1093.  
  1094.  To record an audited event, your service calls NetAuditWrite, passing the
  1095.  appropriate audit event type, a pointer to the event specific data, and the
  1096.  length of the data. To simplify the logic of your program, NetAuditWrite
  1097.  returns NERR_Success even if LAN Manager is not currently maintaining an
  1098.  audit log. The user can disable all auditing when starting the server
  1099.  service.
  1100.  
  1101.  If your service does write audit records, you should also supply either a
  1102.  facility to examine the records in the audit log or at least the necessary
  1103.  constants and structures to interpret the records written by your service.
  1104.  The audit event type used by PBX and the structure that maps the variable
  1105.  portion of the PBX audit record is contained in PBXSRV.H (see Figure 4).
  1106.  
  1107.  Debugging a LAN Manager Service
  1108.  
  1109.  Although messages written to the LAN Manager error log can be helpful during
  1110.  development to track problems, it may be even more useful to build a debug
  1111.  version of your service that writes more than just error messages to the
  1112.  error log. Informational messages could be written at critical checkpoints
  1113.  to track flow of control and processing of requests handled by your service.
  1114.  
  1115.  Since a service is a detached OS/2 process, your service cannot write to
  1116.  standard output or standard error to report problems. It also makes it
  1117.  difficult to use tools such as the Microsoft CodeView debugger. Therefore,
  1118.  as you develop your service you may want to consider writing and debugging
  1119.  the bulk of the application prior to converting it to a LAN Manager service.
  1120.  Also, once your application has been converted to a service, instead of
  1121.  redirecting standard output to the NUL device, you could direct it to a disk
  1122.  file and use printf or fprintf to record debugging information. This
  1123.  technique was used very successfully while developing PBX.
  1124.  
  1125.  The next article will concentrate on the portions of PBX that create and
  1126.  manage the named pipes and demonstrate how to build a sophisticated named
  1127.  pipe server. It will explain multiple threads, managing multiple instances
  1128.  of a pipe, client-server race conditions, and offer a brief discussion on
  1129.  the two types of pipes supported by OS/2 and LAN Manager, byte and message
  1130.  mode pipes.
  1131.  
  1132.  Figure 2
  1133.  
  1134.  Category:    Access Permissions
  1135.  Description:    Functions to examine or modify resource access
  1136.                  permissions
  1137.  C Define:    INCL_NETACCESS
  1138.  
  1139.  Category:    Alert
  1140.  Description:    Functions to notify services and applications of
  1141.                  network events
  1142.  C Define:    INCL_NETALERT
  1143.  
  1144.  Category:    Auditing
  1145.  Description:    Functions to access and control the LAN Manager
  1146.                  audit log
  1147.  C Define:    INCL_NETAUDIT
  1148.  
  1149.  Category:    Character Device
  1150.  Description:    Functions to control shared communication
  1151.                  devices (such as COM ports) and their queues
  1152.  C Define:    INCL_NETCHARDEV
  1153.  
  1154.  Category:    Configuration
  1155.  Description:    Functions to read the LANMAN.INI file
  1156.  C Define:    INCL_NETCONFIG
  1157.  
  1158.  Category:    Connection
  1159.  Description:    Functions to list connections on a LAN Manager server
  1160.  C Define:    INCL_NETCONNECTION
  1161.  
  1162.  Category:    Domain
  1163.  Description:    Functions to retrieve domain information
  1164.  C Define:    INCL_NETDOMAIN
  1165.  
  1166.  Category:    Error Logging
  1167.  Description:    Functions to access and control the LAN Manager
  1168.                  error log
  1169.  C Define:    INCL_NETERRORLOG
  1170.  
  1171.  Category:    File
  1172.  Description:    Functions to monitor and close file, device, and
  1173.                  pipe resources on a server
  1174.  C Define:    INCL_NETFILE
  1175.  
  1176.  Category:    Group
  1177.  Description:    Functions to control groups of user (part of the
  1178.                  LAN Manager user account subsystem)
  1179.  C Define:    INCL_NETGROUP
  1180.  
  1181.  Category:    Handle
  1182.  Description:    Functions to retrieve or set information for
  1183.                  character device or named pipe specified by a handle
  1184.  C Define:    INCL_NETHANDLE
  1185.  
  1186.  Category:    Mailslot
  1187.  Description:    Functions supporting LAN Manager mailslots
  1188.  C Define:    INCL_NETMAILSLOT
  1189.  
  1190.  Category:    Message
  1191.  Descripton:    Functions to send and receive messages, and
  1192.                 manipulate message aliases
  1193.  C Define:    INCL_NETMESSAGE
  1194.  
  1195.  Category:    Print Destination
  1196.  Description:    Functions to control printers that receive
  1197.                  spooled jobs
  1198.  C Define:    Uses PMSPL.H or DOSPMSPL.H
  1199.  
  1200.  Category:    Print Job
  1201.  Description:    Functions to control jobs in a printer queue
  1202.  C Define:    Uses PMSPL.H or DOSPMSPL.H
  1203.  
  1204.  Category:    Printer Queue
  1205.  Description:    Functions to control printer queues
  1206.  C Define:    Uses PMSPL.H or DOSPMSPL.H
  1207.  
  1208.  Category:    Remote Utility
  1209.  Description:    Functions to support remote file copy and move, the
  1210.                  execution of remote programs, and accessing time-of-day
  1211.                  on a remote server
  1212.  C Define:    INCL_NETREMUTIL
  1213.  
  1214.  Category:    Server
  1215.  Description:    Functions to perform adminstrative tasks on a server
  1216.  C Define:    INCL_NETSERVER
  1217.  
  1218.  Category:    Service
  1219.  Description:    Functions to control LAN Manager services
  1220.  C Define:    INCL_NETSERVICE
  1221.  
  1222.  Category:    Session
  1223.  Description:    Functions to control sessions between workstations
  1224.                  and a server
  1225.  C Define:    INCL_NETSESSION
  1226.  
  1227.  Category:    Share
  1228.  Description:    Functions to control shared resources
  1229.  C Define:    INCL_NETSHARE
  1230.  
  1231.  Category:    Statistics
  1232.  Description:    Functions to retrieve or clear statistics for a
  1233.                  server or workstation
  1234.  C Define:    INCL_NETSTATS
  1235.  
  1236.  Category:    Use
  1237.  Description:    Functions to examine or control uses between a
  1238.                  workstation and a server
  1239.  C Define:    INCL_NETUSE
  1240.  
  1241.  Category:    User
  1242.  Description:    Functions to control a user's account in the user
  1243.                  account subsystem
  1244.  C Define:    INCL_NETUSER
  1245.  
  1246.  Category:    Workstation
  1247.  Description:    Functions to control the operation of a workstation
  1248.  C Define:    INCL_NETWKSTA
  1249.  
  1250.  Figure 5
  1251.  
  1252.    ■   OS/2 1.2      PMSPL.LIB      Print functions (DosPrintxxx)
  1253.                      LAN.LIB        All other functions
  1254.  
  1255.    ■   OS/2 1.1      NETSPOOL.LIB   Print functions
  1256.                      LAN.LIB        All other functions
  1257.  
  1258.    ■   DOS 3.1 and up DOSLAN.LIB    All functions
  1259.  
  1260.    ■   Windows 3.0   PMSPL.LIB      Print functions
  1261.                      LAN.LIB        All other functions
  1262.  
  1263.    ■   Windows 2.x   PMSPL.LIB      Print functions
  1264.                      LAN.LIB        All other functions
  1265.  
  1266.  Figure 6
  1267.  
  1268.      [pbx]
  1269.        lines = 100
  1270.        linebufsize = 1024
  1271.        openlines = 20
  1272.        connsperthread = 5
  1273.        auditing = no
  1274.  
  1275.  Figure 7
  1276.  
  1277.  
  1278.  struct service_status {
  1279.      unsigned short  svcs_status; /* Current service state          */
  1280.      unsigned long   svcs_code;   /* Install/Uninstall code         */
  1281.      unsigned short  svcs_pid;    /* Process identifier of service */
  1282.      char           svcs_text[64];/* Additional text buffer        */
  1283.  };
  1284.  
  1285.  
  1286.  
  1287.  Figure 8
  1288.  
  1289.    ■ General status           SERVICE_UNINSTALLED
  1290.                               SERVICE_INSTALL_PENDING
  1291.                               SERVICE_UNINSTALL_PENDING
  1292.                               SERVICE_INSTALLED
  1293.  
  1294.    ■ Paused/Active status     SERVICE_ACTIVE
  1295.                               SERVICE_CONTINUE_PENDING
  1296.                               SERVICE_PAUSE_PENDING
  1297.                               SERVICE_PAUSED
  1298.  
  1299.    ■ Uninstallable indication SERVICE_NOT_UNINSTALLABLE
  1300.                               SERVICE_UNINSTALLABLE
  1301.  
  1302.    ■   Pausable status        SERVICE_NOT_PAUSABLE
  1303.                               SERVICE_PAUSABLE
  1304.  
  1305.  Figure 11
  1306.  
  1307.  
  1308.  // Extract signal argument
  1309.  fSigArg = (UCHAR)(usSigArg & 0x00FF);
  1310.  
  1311.  // And take the appropriate action
  1312.  switch (fSigArg) {
  1313.  
  1314.  // Uninstall PBX
  1315.  case SERVICE_CTRL_UNINSTALL:
  1316.  
  1317.  
  1318.  
  1319.  // Pause PBX
  1320.  case SERVICE_CTRL_PAUSE:
  1321.  
  1322.  
  1323.  
  1324.  // Continue (a paused) PBX
  1325.  case SERVICE_CTRL_CONTINUE:
  1326.  
  1327.  
  1328.  
  1329.  // Return service information
  1330.  // Unrecognized arguments should be treated as
  1331.  // interrogate requests
  1332.  case SERVICE_CTRL_INTERROGATE:
  1333.  default:
  1334.  
  1335.  
  1336.  
  1337.  }
  1338.  
  1339.  // Acknowledge with OS/2 when signal processing is
  1340.  // complete
  1341.  DosSetSigHandler(0, 0, 0, SIGA_ACKNOWLEDGE, usSigNum);
  1342.  
  1343.  
  1344.  
  1345.  
  1346.  Improve Windows Application Memory Use with Subsegment Allocation and Custom
  1347.  Resources
  1348.  
  1349.  Paul Yao
  1350.  
  1351.  Improved memory usage is one of the most important enhancements in the
  1352.  Microsoft Windows graphical environment Version 3.0. Protected mode Windows1
  1353.  lets you access up to 16Mb of RAM, and in 386 enhanced mode, a virtual
  1354.  address space as large as four times the available physical RAM can be
  1355.  created. On a machine with 16Mb of RAM and sufficient room on the swap disk,
  1356.  a 64Mb virtual address space is possible. This is quite an improvement over
  1357.  the 640Kb limitation that plagued earlier versions.
  1358.  
  1359.  Windows lets you choose among several types of memory. This article explores
  1360.  application memory use in Windows and identifies every place that you can
  1361.  store a byte of data, concluding with sample programs that demonstrate
  1362.  subsegment allocation and the creation of custom resources.
  1363.  
  1364.  Subsegment allocation uses the Windows local heap management routines, such
  1365.  as LocalAlloc and LocalFree, to manage a dynamically allocated segment.
  1366.  Programmers working in OS/2 systems will recognize this as something
  1367.  performed by the DosSubAlloc routine, and OS/2 Presentation Manager
  1368.  programmers may recall that the WinAllocMem routine provides this service.
  1369.  In the second half of this article, I'm going to show how to use a little
  1370.  assembly language programming to access this service in a Windows program.
  1371.  
  1372.  Windows has built-in support for several types of resources: dialog box
  1373.  templates, menu templates, icons, cursors, and so on, as well as
  1374.  custom-written resources. Resources are quite memory-efficient because
  1375.  they are read-only data objects that can be demand-loaded at any time, and
  1376.  can be discarded (purged) from memory when system memory is low. When a
  1377.  resource is needed again, it can be reloaded from disk. I'll demonstrate
  1378.  creating and using custom resources with a sample program.
  1379.  
  1380.  
  1381.  
  1382.  Memory Management Factors
  1383.  
  1384.  Four factors are important when dealing with memory: allocation, visibility,
  1385.  lifetime, and overhead.
  1386.  
  1387.  "Allocation" refers to who sets aside a piece of memory (see Figure 1). In
  1388.  some cases, the compiler allocates memory in response to the declaration of
  1389.  variables. In other cases, memory is allocated in response to calls made to
  1390.  dynamic allocation routines. Windows has two distinct dynamic allocation
  1391.  packages: one to allocate segments, and one to partition segments into
  1392.  smaller pieces. Memory allocation can also occur as a side effect of
  1393.  creating graphical and user interface objects. For example, when you create
  1394.  a pen in the Graphics Device Interface, space is set aside in GDI's local
  1395.  heap. When you create a window or a menu, the Windows user interface manager
  1396.  allocates memory in its own local heap.
  1397.  
  1398.  "Visibility" describes who can access memory. Some objects have a very
  1399.  limited visibility, such
  1400.  
  1401.  as automatic variables declared inside a function. Other objects, such as
  1402.  dynamically allocated segments, are visible systemwide. These objects are
  1403.  the most suitable for sharing data among programs, and are central to the
  1404.  implementation of the Windows Clipboard and Dynamic Data Exchange (DDE)
  1405.  mechanisms.
  1406.  
  1407.  "Lifetime" pertains to the way memory is reclaimed. Generally, when a
  1408.  program terminates, Windows frees the memory the program allocated. Proper
  1409.  reclamation of memory is essential to the health and well-being of Windows
  1410.  as a whole. A few pitfalls require extra care when programming. For example,
  1411.  programs must explicitly free GDI objects and user interface objects such as
  1412.  menus before ending. A future version of Windows will hopefully reclaim
  1413.  memory allocated for these types of objects as well.
  1414.  
  1415.  "Overhead" refers to the extra cost of using a specific type of memory over
  1416.  and above the actual bytes used. It is a factor primarily in the context of
  1417.  dynamically allocated memory. For example, every global memory object has a
  1418.  minimum overhead of 24 bytes. If you are used to allocating many small,
  1419.  dynamic memory objects, you need to rethink your use of dynamic memory. In
  1420.  most cases, arrays of data structures are a more efficient means of storing
  1421.  data in Windows than linked lists, which are popular with C programmers.
  1422.  
  1423.  Since Windows is built on top of the segmented architecture of the Intel-86
  1424.  family of processors, the following discussion is organized in terms of the
  1425.  segments that are present in the Windows global heap.
  1426.  
  1427.  Default Data Segment
  1428.  
  1429.  Windows uses two types of executable files: application programs and
  1430.  dynamic-link libraries (DLLs). Applications are the active "clients" that
  1431.  directly interact with users. DLLs are the passive "servers" that provide
  1432.  code, data, and device support to applications. Both are referred to as
  1433.  modules.
  1434.  
  1435.  In Windows, every module can have a private data segment, which is sometimes
  1436.  referred to as a default data segment. The C compiler, the linker, and the
  1437.  Windows multitasking switcher together ensure that every module has access
  1438.  to its correct data segment. From an architectural point of view, this means
  1439.  that the processor's data segment (DS) register is set up every time a
  1440.  module boundary is crossed. From a programming point of view, module
  1441.  boundaries can only
  1442.  
  1443.  be crossed by calling exported functions. Exported functions are listed in
  1444.  the EXPORTS section of a module definition file or defined with the _exports
  1445.  pragma.
  1446.  
  1447.  Because of the manner in which Windows maintains the DS register for
  1448.  different modules, you must use either the small or medium compiler model.
  1449.  These memory models, after all, are built for a single data segment. Other
  1450.  compiler models such as compact, large, and huge can be used, but they
  1451.  impose certain restrictions on programs. One reason to use these models is
  1452.  that they support multiple data segments. However, Windows allows only one
  1453.  instance of a program to run if it has multiple data segments. Furthermore,
  1454.  in real mode multiple data segments must be declared FIXED in the program's
  1455.  DEF file. Unfortunately, this causes problems using the local heap, since a
  1456.  fixed heap cannot grow beyond its initial size. This is why most Windows
  1457.  programmers have concluded that these compiler models aren't worth the
  1458.  trouble.
  1459.  
  1460.  In Figure 2, HeapWalker is shown displaying the segments belonging to the
  1461.  Windows Calculator. The highlighted segment is CALC's default data segment.
  1462.  In many respects, a program's default data segment is like any other segment
  1463.  in the global heap: it is allocated with a call to GlobalAlloc and has a
  1464.  maximum size of 64Kb.
  1465.  
  1466.  In other ways, a module's default data segment is different from other
  1467.  segments. For one, it is automatically locked whenever a program receives a
  1468.  message. Unlike most data segments, a program's default data segment is
  1469.  automatically locked, so you don't have to lock it explicitly.
  1470.  
  1471.  Another way in which the default data segment differs from other segments is
  1472.  that it is accessible with near data pointers. That's because the DS
  1473.  register points to the default data segment. When a Windows program calls a
  1474.  DLL routine (such as TextOut), the processor's DS register is set up to
  1475.  point to the library's default data segment. The same thing happens when
  1476.  Windows calls a program's WinMain, or when it calls a WinProc. Handshaking
  1477.  ensures that the DS register is assigned so that it references the module's
  1478.  default data segment.
  1479.  
  1480.  A typical default data segment consists of a header, a static data area, a
  1481.  stack, a local heap, and an optional atom table (see Figure 3).
  1482.  
  1483.  The header is a 16-byte data area containing pointers that Windows uses to
  1484.  manage a default data segment. A Windows program must leave this area alone
  1485.  since it is automatically allocated at compile/link time and managed by
  1486.  Windows at run time.
  1487.  
  1488.  The header contains five pointers. Perhaps the most important is pLocalHeap,
  1489.  which points to the beginning of the local heap in the default data segment.
  1490.  The local heap management routines use this pointer to find the heap. A
  1491.  Windows program should not use it directly to manipulate the heap but
  1492.  instead should call the Windows library routines.
  1493.  
  1494.  Three of the pointers reference the stack: pStackBot, pStackMin, and
  1495.  pStackTop. When running the debug version of Windows, these pointers are
  1496.  used to check for stack overflow. It is a good idea to test your programs in
  1497.  this special version to detect stack overflow and other error conditions.
  1498.  
  1499.  The fifth pointer, pAtomTable, points to a local atom table if the program
  1500.  has created one. An atom table stores variable length strings in a common
  1501.  hash table. An atom is identified by a 2-byte handle, which allows variable
  1502.  length strings to be referenced using a fixed-length 2-byte value. The atom
  1503.  table is itself part of the default data segment's local heap.
  1504.  
  1505.  The header does not contain any pointers to the static data area. The
  1506.  compiler defines the static data area and generates code that correctly
  1507.  accesses it.
  1508.  
  1509.  The Static Data Area
  1510.  
  1511.  The static data area contains the static variables, which C programmers
  1512.  sometimes refer to as global variables. The static variables are those
  1513.  declared outside a function boundary, and all variables declared with the
  1514.  static keyword. In addition, all literal strings are stored as static data
  1515.  objects.
  1516.  
  1517.  In the following code fragment, four objects are allocated in the static
  1518.  data area:
  1519.  
  1520.  
  1521.  char *pchFile;
  1522.  long lLength;
  1523.  
  1524.  long FAR PASCAL WndProc (HWND hwnd,   WORD wMsg,
  1525.                           WORD wParam, LONG lParam)
  1526.  {
  1527.      static int iCount;
  1528.      PAINTSTRUCT ps;
  1529.  
  1530.      switch (wMsg)
  1531.      {
  1532.      case WM_PAINT:
  1533.           BeginPaint (hwnd, &ps);
  1534.           TextOut (ps.hdc, 10, 10, "Windows", 7);
  1535.           EndPaint (hwnd, &ps);
  1536.  
  1537.  
  1538.  
  1539.  
  1540.  
  1541.  
  1542.  The two variables defined outside the procedure, pchFile and lLength, are
  1543.  allocated in the static data area, as you would expect. So is iCount, which
  1544.  uses the static keyword. This keyword makes iCount visible only within this
  1545.  routine. But as a static object, it has a lifetime as long as the program
  1546.  itself and therefore it resides in the static data area.
  1547.  
  1548.  The fourth object that is allocated in the static data area is the string
  1549.  "Windows". Every literal string gets its own data area even if two strings
  1550.  are the same. If a string is going to be used in several places, it is a
  1551.  good idea to define it once as an array and then reference the array by
  1552.  name. Even better, use a string table to minimize the impact of literal
  1553.  strings on the size of the static data area. A string table is a resource
  1554.  that keeps strings on disk until they are needed. When needed, a string is
  1555.  read from disk into its own discardable data segment. This way, when your
  1556.  program is finished using them, string resources can be purged from system
  1557.  memory.
  1558.  
  1559.  The Stack
  1560.  
  1561.  The stack is a dynamic data area that is managed by high-level languages
  1562.  like C. The stack is so important that 80x86 processors have a set of
  1563.  registers dedicated to its support and maintenance: the SS (stack segment)
  1564.  register, the BP (base pointer) register, and the SP (stack pointer)
  1565.  register.
  1566.  
  1567.  When the call machine instruction is executed, a return address is
  1568.  automatically pushed onto the stack by the CPU. When the return (ret)
  1569.  instruction is executed, the return address is popped from the stack to
  1570.  determine the instruction to which control is to be passed.
  1571.  
  1572.  The C compiler and the CPU store three things on the stack: automatic or
  1573.  "local" variables, arguments to called functions, and return addresses. The
  1574.  STACKSIZE statement in a program's DEF file sets the size of a stack, with a
  1575.  minimum stack of 5Kb allocated by the Windows loader.
  1576.  
  1577.  Automatic variables are allocated on entry into a function and freed upon
  1578.  exit. All variables declared inside the boundaries of a function without the
  1579.  static keyword are automatic variables. In the code fragment shown earlier,
  1580.  the PAINTSTRUCT variable ps is an automatic variable. If a program uses a
  1581.  lot of automatic variables, the STACKSIZE may need to be increased to
  1582.  reflect this.
  1583.  
  1584.  Figure 4 shows a function being called in C, the corresponding assembly
  1585.  language instructions that are generated, and the arrangement of the stack
  1586.  after a function has been called and the stack set up. Each push instruction
  1587.  copies two bytes to the stack. In this case, the called function, y, takes
  1588.  three integer parameters, so three push instructions are generated to put
  1589.  these parameters in place. The call instruction places a return address on
  1590.  the stack, then passes control to the called function.
  1591.  
  1592.  Inside the called function, the compiler creates code to adjust the BP
  1593.  register to establish the stack frame. The base pointer then serves as the
  1594.  anchor point from which parameters can be referenced as positive offsets
  1595.  from the base pointer. For example, this assembly language instruction
  1596.  places the third parameter into AX.
  1597.  
  1598.  mov ax, [bp+04]
  1599.  
  1600.  
  1601.  If there are local variables, the compiler creates code to adjust the SP
  1602.  register to reserve space on the stack for them. This is done by the
  1603.  following assembly language instruction, which is found in Figure 4:
  1604.  
  1605.  sub sp,6
  1606.  
  1607.  
  1608.  Local variables are accessed as negative offsets from the base pointer. For
  1609.  example, this instruction copies the third local variable in the example
  1610.  into the AX register.
  1611.  
  1612.  mov ax, [bp-06]
  1613.  
  1614.  
  1615.  Though the workings of the stack are somewhat esoteric and complex, the C
  1616.  compiler usually insulates you from needing to understand every detail of
  1617.  its operation. Hopefully this brief explanation has clarified the role of
  1618.  the stack, and therefore the rationale behind the size that you specify in
  1619.  the STACKSIZE statement.
  1620.  
  1621.  The Local Heap
  1622.  
  1623.  The local heap provides a private area in the default data segment from
  1624.  which memory can be dynamically allocated. Every program's default data
  1625.  segment has a local heap, which is automatically set up by the program
  1626.  loader. A local heap is set up by calling the LocalInit routine. Allocating
  1627.  and using memory in a local heap involves making calls to the local heap
  1628.  management routines (see Figure 5).
  1629.  
  1630.  Windows local heap management routines support three types of memory
  1631.  objects: fixed, moveable, and discardable. These are the same types that can
  1632.  be allocated on the global heap. Windows local heap routines are much more
  1633.  sophisticated than the C malloc routine, which allocates only fixed objects.
  1634.  
  1635.  To support moveable and discardable objects, the local heap manager uses a
  1636.  handle-based memory allocation scheme. In other words, when a call is made
  1637.  to the local heap allocation routine LocalAlloc, it returns a memory handle,
  1638.  not a pointer. A memory handle is an identifier. By hiding the location of
  1639.  the memory object, the local heap manager can move or discard objects when
  1640.  necessary to satisfy allocation requests.
  1641.  
  1642.  To access a local memory object, you lock the object by calling LocalLock.
  1643.  This routine does two things: it increments the lock count of an object and
  1644.  it returns a pointer to the object. You can keep an object locked for as
  1645.  long as you need to access the object. However, since locking prevents the
  1646.  local heap from being reorganized, it's often a good idea to keep a lock on
  1647.  for a very short time, perhaps only for the duration of a single message.
  1648.  Remove a lock by calling LocalUnlock.
  1649.  
  1650.  To free a memory object that has been allocated on the local heap, you must
  1651.  first unlock it, and then call LocalFree. The following code fragment
  1652.  demonstrates allocating a memory object and copying a string into the
  1653.  object.
  1654.  
  1655.  
  1656.  HANDLE hMem;
  1657.  PSTR   pstr;
  1658.  
  1659.  /*  Allocate a 15-byte moveable object.  */
  1660.  hMem = LocalAlloc (LMEM_MOVEABLE, 15);
  1661.  
  1662.  /*  Lock the object, getting a pointer.  */
  1663.  pstr = LocalLock (hMem);
  1664.  if (!pstr)
  1665.      return (ERROR);
  1666.  lstrcpy (pstr, "Hello World");
  1667.  
  1668.  /*  Unlock the object.  */
  1669.  LocalUnlock (hMem);
  1670.  
  1671.  
  1672.  
  1673.  As you can see, error checking is important when you allocate memory. You
  1674.  have no guarantee that the system will be able to satisfy your allocation
  1675.  request. And if you write to the NULL pointer returned by LocalLock when it
  1676.  fails, you overwrite the bytes at the bottom of the data segment. Remember,
  1677.  the segment header is located at the base of your default data segment.
  1678.  Overwriting it could be disastrous.
  1679.  
  1680.  Local Atom Table
  1681.  
  1682.  A program can create an atom table in its default data segment with a call
  1683.  to InitAtomTable. This routine creates an atom table in the local heap. An
  1684.  atom table efficiently stores variable-length strings. Once stored, a 2-byte
  1685.  atom is returned, which is in some ways analogous to a handle. When a
  1686.  duplicate string is added to an atom table, the same atom value is returned,
  1687.  minimizing the duplication of variable-length strings.
  1688.  
  1689.  In addition to the local atom table, Windows provides a global atom table.
  1690.  DDE uses the global atom table to pass the names of data topics between
  1691.  programs.
  1692.  
  1693.  Dynamically Allocated Segments
  1694.  
  1695.  Dynamically allocated segments are the most flexible type of read/write
  1696.  memory for a program. Up to the limit of available system memory, a program
  1697.  can have as many dynamically allocated segments as it wants, and each
  1698.  allocated segment can be as large as 64Kb. In fact, you can allocate
  1699.  segments that are larger than 64Kb, although that is beyond the scope of
  1700.  this article.
  1701.  
  1702.  Windows 3.0 has a systemwide limit of 8192 segments in real mode and in 386
  1703.  enhanced mode. In standard mode, the limit is 4096 segments. The standard
  1704.  mode limitation reflects the fact that two entries in the system handle
  1705.  table, also known as the Local Descriptor Table (LDT), are required for each
  1706.  segment. One entry is for the allocated segment and the other is for a
  1707.  16-byte header that is attached to every allocated segment. Because real
  1708.  mode and 386 enhanced mode do not require this extra handle table entry,
  1709.  they are able to support twice the number of segments as standard mode.
  1710.  
  1711.  In a future version of Windows, the limit to standard mode segments should
  1712.  eventually be raised to 8192. This limit will change from a systemwide limit
  1713.  to a per-task limit for both standard mode and 386 enhanced mode. Windows
  1714.  will then have one LDT per task instead of the current implementation of one
  1715.  LDT for the entire system in protected mode.
  1716.  
  1717.  Programs allocate and manage dynamic segments using Windows global heap
  1718.  management routines (see Figure 6). Where applicable, routines in Figure 6
  1719.  are paired as a "top slice" and a "bottom slice" in a construction I call
  1720.  the Windows sandwich.
  1721.  
  1722.  My Windows sandwich has three parts: two outside pieces of bread that hold
  1723.  the third part, the filling (see Figure 7). The first piece of bread
  1724.  represents the code that borrows a system resource. The second piece of
  1725.  bread represents the code that relinquishes the resource. The filling
  1726.  represents code with which a program uses the resource. The idea is that the
  1727.  filling is always used inside the sandwich, never by itself. The most common
  1728.  type of sandwich is probably the following, a standard response to a
  1729.  WM_PAINT message:
  1730.  
  1731.  
  1732.  case WM_PAINT:
  1733.     {
  1734.     PAINTSTRUCT ps;
  1735.     BeginPaint (hwnd, &ps);          // top slice
  1736.     TextOut (ps.hdc, x, y, "Hi", 2); // filling
  1737.     EndPaint (hwnd, &ps);            // bottom slice
  1738.         .
  1739.         .
  1740.         .
  1741.  
  1742.  
  1743.  
  1744.  Segment allocation mirrors local heap allocation in that both use a
  1745.  handle-based memory management scheme. A call to GlobalAlloc returns a
  1746.  handle, which identifies the memory but does not reveal its location.
  1747.  Segments can be allocated as fixed, moveable, or discardable. To determine
  1748.  the attributes of a specific segment, you can run HeapWalker and look for
  1749.  objects that are identified as type Task. Using Figure 8 as a guide, the
  1750.  three types of memory attributes can be easily identified. Note that a
  1751.  discardable segment is also a moveable segment.
  1752.  
  1753.  To access a global memory object, a call must be made to GlobalLock. Like
  1754.  its local heap counterpart, LocalLock, GlobalLock increments a memory
  1755.  object's lock count and returns a pointer to the object. Again, it is a
  1756.  good idea to keep a lock only as long as an object is being used, which in
  1757.  most cases means for the duration of a message. Unlocking an object involves
  1758.  making a call to GlobalUnlock, which decrements the lock count on an object.
  1759.  
  1760.  The following code fragment allocates a global memory object and copies a
  1761.  string into the allocated memory.
  1762.  
  1763.  
  1764.  HANDLE hMem;
  1765.  LPSTR  lpstr;
  1766.  
  1767.  /*  Allocate a 15-byte moveable object.  */
  1768.  hMem = GlobalAlloc (GMEM_MOVEABLE, 15);
  1769.  
  1770.  /*  Lock the object, getting a pointer.  */
  1771.  lpstr = GlobalLock (hMem);
  1772.  if (!lpstr)
  1773.      return (ERROR);
  1774.  
  1775.  lstrcpy (lpstr, "Hello World");
  1776.  
  1777.  /*  Unlock the object.  */
  1778.  GlobalUnlock (hMem);
  1779.  
  1780.  
  1781.  
  1782.  Error checking is just as important when using global heap objects. There is
  1783.  no guarantee that Windows will be able to satisfy all of your requests for
  1784.  memory, so you should plan accordingly. The above code fragment checks the
  1785.  return value of GlobalLock; many Windows programmers also check for a valid
  1786.  (nonzero) return value from GlobalAlloc.
  1787.  
  1788.  For a program to run properly in all operating modes, it's a good idea to
  1789.  keep global memory objects only for the duration of a single message.
  1790.  However, if a program is to run solely in protected mode, it can take a
  1791.  shortcut that is unthinkable in real mode. It can lock global memory objects
  1792.  once, upon allocation, and keep them locked for as long as the object needs
  1793.  to be in memory.
  1794.  
  1795.  This can be fatal in real mode because global memory objects are locked in
  1796.  the physical address space and quickly create memory sandbars. In protected
  1797.  mode, however, such objects are not locked in the physical address space.
  1798.  These objects can be moved unbeknownst to the application program.
  1799.  
  1800.  Resources
  1801.  
  1802.  A resource is a read-only data object that has been merged into a module's
  1803.  executable (DLL, DRV, FON, or EXE) file by the resource compiler. When the
  1804.  data is needed, it can be read into a discardable segment. When the memory
  1805.  manager needs to use that memory for other purposes, it can discard or purge
  1806.  the segment containing the resource.
  1807.  
  1808.  Locating a resource with HeapWalker is easy, since resources are labeled
  1809.  clearly. HeapWalker knows if a particular resource is a menu template,
  1810.  dialog box template, or whatever. HeapWalker can even show the bitmap
  1811.  associated with a particular resource (see Figure 9). Windows has built-in
  1812.  support for accelerator tables, bitmaps, cursors, dialog box templates,
  1813.  fonts, icons, menu templates, and string tables. These resources are used to
  1814.  support several Windows user-interface objects: menus and dialog boxes, to
  1815.  name two. In addition, several GDI objects are stored as resources: fonts
  1816.  and bitmaps, for example.
  1817.  
  1818.  Each resource resides in a separate segment. A program with one menu
  1819.  template, three dialog box templates, and two cursors has six segments'
  1820.  worth of resources. Each resource is put in its own segment so that each can
  1821.  be loaded and discarded individually.
  1822.  
  1823.  If you are writing a Windows program that has a fairly large block of
  1824.  read-only data, you should consider putting the data into a resource. If
  1825.  your data does not fit easily
  1826.  
  1827.  into one of the predefined resource types, it's simple to create a custom
  1828.  resource. I will demonstrate this in the CUSTRES program.
  1829.  
  1830.  GDI Data Segment
  1831.  
  1832.  Windows programs can call GDI routines that cause objects to be allocated in
  1833.  GDI's default data segment. Whenever a call is made to a GDI routine that
  1834.  contains the word Create (such as CreateDC, CreatePen, CreateFontIndirect),
  1835.  you know that the routine allocates memory in GDI's local heap (see Figure
  1836.  10). Fonts and bitmaps also cause memory to be allocated from the global
  1837.  heap.
  1838.  
  1839.  In general, if a program creates an object, it should make sure that it
  1840.  deletes the object. In all versions of Windows, GDI expects every program to
  1841.  clean up after itself. If a program forgets to delete an object, the
  1842.  allocated memory is lost to the system. GDI was designed this way to allow
  1843.  programs and instances of programs to share GDI drawing objects. For
  1844.  example, the first instance of a program could create the GDI drawing
  1845.  objects to be used by all instances. When subsequent instances start up,
  1846.  they access the existing drawing objects either by sending messages or
  1847.  calling the GetInstanceData routine, which copies the value of static data
  1848.  objects from one instance's data segment to another instance's data segment.
  1849.  
  1850.  Unfortunately, this design has resulted in a problem. If a program does not
  1851.  free a GDI object when it terminates, the memory is lost forever. For this
  1852.  reason, in a future version of Windows, Microsoft should consider changing
  1853.  GDI so that, upon termination of a program, all GDI objects created by the
  1854.  program are automatically destroyed. Until that time, you should ensure that
  1855.  your program explicitly deletes all GDI objects that it has created (and
  1856.  that it has not passed to other programs on the Clipboard).
  1857.  
  1858.  USER Data Segment
  1859.  
  1860.  The Windows USER module supports Windows user-interface objects, such as
  1861.  windows, menus, dialog boxes, cursors, carets, and accelerator tables.
  1862.  Unlike GDI objects, USER objects are not designed to be shared between
  1863.  programs. For the most part, when a program terminates, USER frees the
  1864.  memory that has been allocated for the user interface objects. Nevertheless,
  1865.  you should be aware of the ways that a Windows program can cause memory to
  1866.  be allocated in USER's local heap, because it is a limited resource that is
  1867.  shared by all applications.
  1868.  
  1869.  Quite a few user interface objects are stored in their own segments. In
  1870.  fact, this is true for all user interface objects that are created as
  1871.  resources. Others are allocated in USER's local heap, as shown in Figure 11.
  1872.  While these objects are small, there are a few "gotchas" to avoid.
  1873.  
  1874.  If you create several different menus and attach some of them to a window
  1875.  using SetMenu, you have to be careful when your program terminates. Menus
  1876.  that are connected to a window are automatically freed. Menus that are not
  1877.  connected to a window, however, must be explicitly freed by your program
  1878.  when it terminates.
  1879.  
  1880.  A second "gotcha" occurs when you register a window class. As you may
  1881.  recall, class registration involves filling in the values of a structure of
  1882.  type WNDCLASS and passing a pointer to the RegisterClass routine. Make sure
  1883.  that you fill in all elements of the WNDCLASS structure. If you don't fill
  1884.  in the values of two fields, cbWndExtra and cbClassExtra, you may be in for
  1885.  a surprise. These values define the amount of extra bytes to be allocated in
  1886.  USER's data segment: cbWndExtra defines the extra bytes to be allocated for
  1887.  each window, and cbClassExtra defines the extra bytes to be allocated for
  1888.  the class. If you forget to initialize these fields, the USER module
  1889.  allocates extra bytes using whatever values it finds in the data structure.
  1890.  
  1891.  Of course, you may wish to use the often-overlooked window or class extra
  1892.  bytes. They may be accessed via the functions found in Figure 12. They can
  1893.  be used to connect a window efficiently to its data. For example, two window
  1894.  extra bytes might be allocated to hold a memory handle for the data to be
  1895.  displayed in a window. Or two class extra bytes could be allocated for GDI
  1896.  drawing objects shared by the windows in a class.
  1897.  
  1898.  Window extra bytes can be very useful when creating custom dialog box
  1899.  controls, or for creating MDI windows. In general, if you are creating a
  1900.  window class that will support multiple windows in a single application,
  1901.  window extra bytes provide an easy way to connect a window's data to the
  1902.  window itself.
  1903.  
  1904.  Subsegment Allocation
  1905.  
  1906.  As already discussed, there are two dynamic memory allocation packages in
  1907.  Windows: local heap allocation and global memory allocation. By default,
  1908.  every Windows program has a local heap. The heap is created by a routine
  1909.  called InitApp, which is undocumented  but is part of the standard start-up
  1910.  sequence of every program. One advantage of the local heap is that the
  1911.  overhead for objects is low (4 to 6 bytes), and with a granularity of 4
  1912.  bytes, waste is kept to a minimum. The only problem with the local heap is
  1913.  that it is too small for many uses. Depending on the size of your stack and
  1914.  the static data, the room remaining for the largest default local heap might
  1915.  be 30Kb--50Kb.
  1916.  
  1917.  This problem can be solved by using the global heap. On 386 systems, this
  1918.  implies taking advantage of disk swap space in addition to physical RAM.
  1919.  With the global heap, however, the overhead per object is high. Also, at a
  1920.  minimum of 32 bytes per segment, the granularity of segments is too high to
  1921.  be used for very small objects. Only large objects or arrays of small
  1922.  objects are suitable for storage in objects allocated from the global heap.
  1923.  
  1924.  To get the benefits of both local and global heap management, it's possible
  1925.  to create a local heap in a dynamically allocated global segment. You can
  1926.  allocate small objects from this heap, all managed by the local heap
  1927.  manager, that share the segment efficiently with other objects.
  1928.  
  1929.  To do this, keep in mind that the first 16 bytes of a segment are reserved
  1930.  for the use of the local heap manager. At offset 06H, it stores a pointer to
  1931.  the local heap. This allows the local heap to sit at the end of an
  1932.  application's default data segment, after its static data and stack.
  1933.  
  1934.  A second concern involves local heap initialization, which is accomplished
  1935.  by calling LocalInit. This code fragment allocates a segment and initializes
  1936.  a local heap in it.
  1937.  
  1938.  
  1939.  HANDLE hMem;
  1940.  int    pStart, pEnd;
  1941.  LPSTR lp;
  1942.  WORD  wSeg;
  1943.  
  1944.  hMem = GlobalAlloc (GMEM_MOVEABLE, 4096L);
  1945.  if (!hMem)
  1946.      goto ErrorOut;
  1947.  
  1948.  lp = GlobalLock (hMem);
  1949.  wSeg = HIWORD (lp);
  1950.  
  1951.  pStart = 16;
  1952.  pEnd  = (int)GlobalSize (hMem)-1;
  1953.  LocalInit (wSeg, pStart, pEnd);
  1954.  GlobalUnlock(hMem);
  1955.  GlobalUnlock(hMem);
  1956.  
  1957.  
  1958.  
  1959.  Notice the two calls to GlobalUnlock. The first counteracts the call to
  1960.  GlobalLock; the second is needed because LocalInit leaves a segment locked.
  1961.  Without the second call, the data segment would still be locked. In
  1962.  protected mode, this doesn't present a problem, but segments that are
  1963.  unnecessarily locked can create memory sandbars in real mode.
  1964.  
  1965.  As always, GlobalAlloc's return value should be checked to determine whether
  1966.  the requested memory is available. Even though you asked for a 4096-byte
  1967.  segment, because different operating modes align on different segment
  1968.  boundaries, call GlobalSize to make sure you know the exact size of the
  1969.  segment. To make room for the header, pStart is set to 16. Set pEnd to the
  1970.  offset of the last byte in the segment, which is the segment size minus one.
  1971.  
  1972.  Another way to do this is to set pStart to zero, and set pEnd to the actual
  1973.  size of the local heap.
  1974.  
  1975.  pEnd = (int)GlobalSize(hMem)-16;
  1976.  LocalInit(wSeg, 0, pEnd);
  1977.  
  1978.  
  1979.  You subtract 16 from the size of the segment to set aside space for the
  1980.  segment's header.
  1981.  
  1982.  Accessing the local heap requires some programming in assembly language
  1983.  because the segment selector of the heap must be placed in DS. Any of the
  1984.  local heap management routines may then be called to operate on the local
  1985.  heap.
  1986.  
  1987.  When the DS register is pointing at a heap segment, don't try referencing
  1988.  any static variables. You won't be able to access them, but you will
  1989.  overwrite some of the heap segment instead.
  1990.  
  1991.  If you are using Microsoft C Version 6.0, you can use the _asm pragma to
  1992.  embed assembly language in your C code. The following saves DS on the stack,
  1993.  sets up the DS register to point to the heap segment, allocates memory from
  1994.  the heap, and restores DS to point to the default data segment.
  1995.  
  1996.  
  1997.  LPSTR  lp;
  1998.  HANDLE hmem;
  1999.  WORD  wHeapDS;   /*  Must be a stack variable!  */
  2000.  
  2001.  lp = GlobalLock (hmem);  /* Where local heap lives */
  2002.  wHeapDS = HIWORD (lp);
  2003.  
  2004.  _asm{
  2005.      push  DS
  2006.  
  2007.      mov   AX, wHeapDS
  2008.      mov   DS, AX
  2009.      }
  2010.  
  2011.  hmem = LocalAlloc (LMEM_MOVEABLE, 16);
  2012.  
  2013.  _asm{
  2014.      pop   DS
  2015.      }
  2016.  
  2017.  GlobalUnlock (hmem);
  2018.  
  2019.  
  2020.  
  2021.  Using a local heap in a dynamically allocated segment requires calls to two
  2022.  routines, one for the segment and one for the local heap object. To obtain a
  2023.  pointer to the allocated memory, calls to GlobalLock and LocalLock are
  2024.  required. And to prevent fragmentation in the global heap or the local heap,
  2025.  you'll probably want to unlock at both levels.
  2026.  
  2027.  There are other methods, of course. A program can make all local heap
  2028.  objects fixed, removing the need to perform the second lock. To be most
  2029.  effective, it makes sense to build a small subroutine library to manage the
  2030.  two-level allocation scheme. This might be as simple as creating your own
  2031.  32-bit handles, using half for the local handle and half for the global
  2032.  handle. This is the approach used in the sample program, SUBSEG (see Figure
  2033.  13). Another alternative is for a subroutine package to issue its own
  2034.  private 16-bit handles that reference a table of segments and local memory
  2035.  objects.
  2036.  
  2037.  SUBSEG
  2038.  
  2039.  The SUBSEG program demonstrates subsegment allocation in a dynamically
  2040.  allocated segment. I borrow the term subsegment allocation from OS/2, since
  2041.  it describes this procedure better than the term local allocation. This
  2042.  program contains a set of subsegment allocation routines that mirror the
  2043.  Windows standard memory allocation routines. A routine called SubAlloc takes
  2044.  the same parameters as LocalAlloc. Four other routines provide the basic
  2045.  allocation services. Another function, SubInitialize, allocates and
  2046.  initializes the dynamically allocated segment.
  2047.  
  2048.  SUBSEG displays information about the allocated data objects (see Figure
  2049.  14). To convince you that it works as advertised, it reads this information
  2050.  from the object itself. As you can see, the actual size of objects is a
  2051.  little larger than requested, reflecting the 4-byte granularity of the local
  2052.  heap manager.
  2053.  
  2054.  Unlike the handles generated by the local and global heap managers, the
  2055.  handles issued by the memory management routines in SUBMEM.C are 32-bit. The
  2056.  HIWORD contains a global memory handle, and the LOWORD contains a local
  2057.  memory handle. You may want to devise your own scheme for identifying memory
  2058.  objects, but for many purposes this is fast and simple enough.
  2059.  
  2060.  If you've examined the code, you may have noticed that the segment allocated
  2061.  by SUBSEG is never freed. Well, do as I say, not as I do. In other words,
  2062.  please be sure to free any memory you allocate, unlock any memory you lock,
  2063.  and in general undo whatever needs undoing to free any resource you use.
  2064.  
  2065.  Custom Resources
  2066.  
  2067.  Custom resources, as I said earlier, let you exploit the built-in memory
  2068.  management features of resources with a minimum of effort. The best custom
  2069.  resources are data objects that won't change. The sample resource I'll
  2070.  demonstrate contains a table for determining sine and cosine values. This
  2071.  look-up table supplies an integer sine value, which is simply a sine value
  2072.  multiplied by 10,000. Look-up tables are commonly used because they are
  2073.  often faster than on-the-fly calculations. Also, because 80386 and earlier
  2074.  Intel processors do not have built-in floating point support, you'll get
  2075.  faster performance if you limit your calculations to integer arithmetic. (By
  2076.  the way, this fact influenced Microsoft enough to build Windows without
  2077.  using any floating point arithmetic.)
  2078.  
  2079.  CUSTRES contains two routines that calculate sine and cosine values for an
  2080.  angle in measured degrees. With two functions and 360 degrees, there are 720
  2081.  different values required for the look-up table. By taking advantage of the
  2082.  symmetry of sines and cosines and doing a little folding and rotating, I
  2083.  produce the same results with a single table of 91 sine values.
  2084.  
  2085.  I obtained the table by writing a C program that calculates sine values and
  2086.  writes them as ASCII text to a data file. Why ASCII text? I'm going to show
  2087.  you a trick that lets you build complex binary data objects from ASCII text
  2088.  files. The only tools required are a macro assembler, the linker, and a
  2089.  special converter called EXE2BIN.EXE that comes with DOS. Figure 15 contains
  2090.  the program files that create the custom resource.
  2091.  
  2092.  The data file created by this program is an assembly language file
  2093.  containing data definitions but no machine code. Instead, I use the macro
  2094.  assembler to convert the data definitions into a binary format. The assembly
  2095.  language file that SINE.EXE creates is SINEDATA.ASM (see Figure 16).
  2096.  
  2097.  After the data file SINEDATA.ASM has been run through the macro assembler
  2098.  and the linker, you have a "ready-to-run" EXE file that contains no code.
  2099.  Next, EXE2BIN is used to isolate the data into a pure binary object; this
  2100.  program is ordinarily used to create COM files from EXE files. A COM file is
  2101.  simply a memory image that can be loaded and run as is. Since that's exactly
  2102.  what you want--a pure, binary image--EXE2BIN does the trick to create the
  2103.  sine table resource.
  2104.  
  2105.  To test the sine and cosine functions, CUSTRES connects 360 points to draw a
  2106.  circle with a radius of 100 pixels (see Figure 17). While this is slower and
  2107.  rougher than you would expect from a call to GDI's Ellipse routine, it does
  2108.  show that the generated sine and cosine values at least look right in the
  2109.  range 0 to 360 degrees.
  2110.  
  2111.  CUSTRES (see Figure 18) does all its work during the WM_CREATE, WM_PAINT,
  2112.  and WM_DESTROY messages. All of the sine and cosine information is contained
  2113.  in two routines, intSin and intCos. The second function actually cheats;
  2114.  since a cosine is always 90 degrees out of phase with a sine, the intCos
  2115.  function subtracts 90 degrees from the actual angle and calls the intSin
  2116.  function.
  2117.  
  2118.  Using a custom resource requires the FindResource, LoadResource, and
  2119.  LockResource routines. The first two are called in response to the WM_CREATE
  2120.  message. The result is a memory handle stored in hresSinData. FindResource
  2121.  searches for the reference to a resource in the module database, which is
  2122.  simply an abbreviated memory image of the module's file header. FindResource
  2123.  takes three parameters:
  2124.  
  2125.  FindResource (hInstance, lpName, lpType)
  2126.  
  2127.  The first, hInstance, is an instance handle. The second, lpName, is a long
  2128.  pointer to a character string with the resource name. The third, lpType, is
  2129.  a long pointer to a character string with the resource type.
  2130.  
  2131.  Even though lpName and lpType are pointers to character strings, this is not
  2132.  the most efficient way to identify a resource, since a string comparison is
  2133.  more "expensive" than an integer comparison. Because of this, I use a macro,
  2134.  MAKEINTRESOURCE, which lets me define integers and use them in place of a
  2135.  character string. Following are the two integers defined in CUSTRES:
  2136.  
  2137.  #define  TABLE  100  /*  Custom resource type.    */
  2138.  #define  SINE   100  /*  ID of sine table.        */
  2139.  
  2140.  These integers are used in the call to FindResource, as follows:
  2141.  
  2142.  hRes = FindResource (hInst,
  2143.             MAKEINTRESOURCE(SIN),      /* Name.  */
  2144.             MAKEINTRESOURCE(TABLE));   /* Type.  */
  2145.  
  2146.  The MAKEINTRESOURCE macro creates a pseudo-pointer, with 0 for a segment
  2147.  identifier and the integer value for the offset value. It casts this value
  2148.  as an LPSTR. When the FindResource routine sees this value, it does not
  2149.  treat it as a pointer (this would be a fatal error). Instead it uses the
  2150.  2-byte integer value to find the resource definition, using the following
  2151.  line in the resource file.
  2152.  
  2153.  SIN  TABLE  sinedata.bin   DISCARDABLE
  2154.  
  2155.  
  2156.  This line causes the data in the resource file, SINEDATA.BIN, to be copied
  2157.  entirely into CUSTRES.EXE at compile/link time. This means that CUSTRES is a
  2158.  standalone program and doesn't need the original resource data file to be
  2159.  present at run time.
  2160.  
  2161.  Once FindResource has provided a resource identifier, a call to LoadResource
  2162.  provides a global memory handle for the resource itself. LoadResource, the
  2163.  next routine called, is defined as follows:
  2164.  
  2165.  LoadResource (hInstance, hresInfo)
  2166.  
  2167.  Its first parameter, hInstance, is the instance handle; the second
  2168.  parameter, hresInfo, is the handle returned by the FindResource routine.
  2169.  
  2170.  In spite of its name, LoadResource does not load a resource. It allocates a
  2171.  global memory object with a size of 0. No memory is set aside; only a global
  2172.  memory handle is assigned. CUSTRES stores this value in hresSinData.
  2173.  
  2174.  The routine that causes a resource to be loaded into memory is LockResource.
  2175.  CUSTRES calls this routine only when it needs to access the sine table data.
  2176.  By postponing the loading of such a memory object, CUSTRES helps minimize
  2177.  the demands it makes on system memory. LockResource itself performs several
  2178.  tasks: it loads the resource into memory, locks it in place, and returns a
  2179.  pointer to the data. LockResource is defined as follows.
  2180.  
  2181.  LPSTR LockResource (hResData)
  2182.  
  2183.  
  2184.  hResData is the handle returned by the LoadResource function. LockResource
  2185.  returns a long pointer to a string. CUSTRES casts this return value to a
  2186.  long pointer to integers.
  2187.  
  2188.      int FAR * fpSin;
  2189.  
  2190.      fpSin = (int FAR *)LockResource (hresSinData);
  2191.      if (fpSin == NULL)
  2192.          return (0);
  2193.  
  2194.  Casting prevents the compiler from complaining about a type-mismatch error.
  2195.  Notice that a check is made on the return value from LockResource, in case
  2196.  something (like insufficient memory) prevented the resource from being
  2197.  loaded.
  2198.  
  2199.  The LockResource routine should always be used with UnlockResource (remember
  2200.  the sandwich?). This construction was discussed earlier as a way to organize
  2201.  the use of a shared resource, in this case memory. LockResource loads a
  2202.  resource and ties it down in memory. UnlockResource unties the resource to
  2203.  allow it to move, or to be discarded. In CUSTRES, the intSin function uses
  2204.  these two routines to bracket its use of the sine data, creating a Windows
  2205.  sandwich that ensures that the object is locked when you need it, and
  2206.  unlocked when not. UnlockResource is defined as follows. hResData is the
  2207.  handle returned by the LoadResource function.
  2208.  
  2209.  BOOL UnlockResource (hResData)
  2210.  
  2211.  
  2212.  The final routine involved with handling custom resources is FreeResource,
  2213.  which frees the memory associated with a custom resource. Again, hResData is
  2214.  the handle returned by the LoadResource function.
  2215.  
  2216.  FreeResource (hResData)
  2217.  
  2218.  
  2219.  CUSTRES calls FreeResource in response to the WM_DESTROY message to
  2220.  deallocate the sine resource. This program doesn't actually need to call
  2221.  FreeResource, since the resource will be freed when CUSTRES terminates, but
  2222.  good programmers clean up after themselves.
  2223.  
  2224.  This program draws using the default MM_TEXT mapping mode. The circle in
  2225.  Figure 17 is perfectly round only if the program is displayed on a VGA
  2226.  monitor with a 1:1 aspect ratio. CUSTRES moves the origin of the logical
  2227.  coordinate system to the middle of the window with the following code:
  2228.  
  2229.  GetClientRect (hwnd, &r);
  2230.  SetViewportOrg (ps.hdc, r.right/2, r.bottom/2);
  2231.  
  2232.  
  2233.  
  2234.  Conclusion
  2235.  
  2236.  As a Windows programmer, you have many options for allocating and working
  2237.  with memory. Knowing how to exploit the different ways of packaging data in
  2238.  Windows should help you write programs that take better advantage of what
  2239.  the system has to offer.
  2240.  
  2241.  1  For ease of reading, "Windows" refers to the Microsoft Windows graphical
  2242.  environment. "Windows" refers to this Microsoft product and is not intended
  2243.  to refer to such products generally.
  2244.  
  2245.  Figure 5.  Local Heap Management Routines
  2246.  
  2247.    ■   LocalAlloc
  2248.        Allocates memory from a local heap.
  2249.    ■   LocalCompact
  2250.        Reorganizes a local heap.
  2251.    ■   LocalDiscard
  2252.        Discards an unlocked, discardable object.
  2253.    ■   LocalFlags
  2254.        Provides information about a specific memory object.
  2255.    ■   LocalFree
  2256.        Frees a local memory object.
  2257.    ■   LocalHandle
  2258.        Provides the handle of a local memory object
  2259.        associated with a given memory address.
  2260.    ■   LocalInit
  2261.        Initializes a local heap.
  2262.    ■   LocalLock
  2263.        Increments the lock count on a local memory
  2264.        object and returns its address.
  2265.    ■   LocalReAlloc
  2266.        Changes the size of a local memory object.
  2267.    ■   LocalShrink
  2268.        Reorganizes a local heap and reduces the size of
  2269.        the heap (if possible) to the initial, starting size.
  2270.        If this routine is successful, it reduces the size of the data segment
  2271.        that contains the heap, so that
  2272.        the memory can be reclaimed by the global heap.
  2273.    ■   LocalSize
  2274.        Returns the current size of a local memory object.
  2275.    ■   LocalUnlock
  2276.        Decrements the lock count on a local memory object.
  2277.  
  2278.  Figure 10. Memory Used in GDI's Local Heap
  2279.  
  2280.        Object           Size
  2281.    ■   Bitmap           28--32 bytes
  2282.    ■   Brush            32 bytes
  2283.    ■   Device context   100 bytes per device for fixed overhead +
  2284.                         200 bytes per DC allocated
  2285.    ■   Font             40--44 bytes
  2286.    ■   Palette          28 bytes
  2287.    ■   Pen              28 bytes
  2288.    ■   Region           28--104 bytes
  2289.  
  2290.  
  2291.  Figure 11. Memory Used in USER's Local Heap
  2292.  
  2293.        Object           Size
  2294.    ■   Menu             20 bytes per menu +20 bytes per menu item
  2295.    ■   Window class     40--50 bytes
  2296.    ■   Window           6070 bytes
  2297.  
  2298.  Figure 12. Memory Used in USER's Local Heap
  2299.  
  2300.    Window Extra Bytes Routines   Description
  2301.    ■   SetWindowWord             Copies two bytes into window extra bytes
  2302.    ■   SetWindowLong             Copies four bytes into window extra bytes
  2303.    ■   GetWindowWord             Retrieves two bytes from window extra bytes
  2304.    ■   GetWindowLong             Retrieves four bytes from window extra bytes
  2305.  
  2306.    Class Extra Bytes Routine     Description
  2307.    ■   SetClassWord              Copies two bytes into class extra bytes
  2308.    ■   SetClassLong              Copies four bytes into class extra bytes
  2309.    ■   GetClassWord              Retrieves two bytes from class extra bytes
  2310.    ■   GetClassLong              Retrieves four bytes from class extra bytes
  2311.  
  2312.  Figure 15. SINE
  2313.  
  2314.  SINE.MAK
  2315.  
  2316.  sine.obj: sine.c
  2317.      cl -c sine.c
  2318.  
  2319.  sine.exe: sine.obj
  2320.      link sine;
  2321.  
  2322.  sinedata.asm: sine.exe
  2323.      sine
  2324.  
  2325.  sinedata.bin: sinedata.asm
  2326.      masm sinedata.asm, sinedata.obj;
  2327.      link sinedata, sinedata.exe;
  2328.      exe2bin sinedata.exe
  2329.  
  2330.  
  2331.  SINE.C
  2332.  
  2333.  /*-------------------------------------------------------------*\
  2334.   |  SINE.C - Creates an .ASM data file containing sine values   |
  2335.   |               from 0 to 90 degrees.  This file is suitable  |
  2336.   |               for creating a custom Windows resource.       |
  2337.  \*-------------------------------------------------------------*/
  2338.  
  2339.  #include "stdio.h"
  2340.  #include "math.h"
  2341.  
  2342.  char achFileHeader[] =
  2343.       ";\n"
  2344.       "; Sine/Cosine Data Table\n"
  2345.       ";\n"
  2346.       ";\n"
  2347.       "; Table of Sine values from 0 to 90 degrees\n"
  2348.       ";\n"
  2349.       "SINDATA segment public\n";
  2350.  
  2351.  char achFileFooter[] =
  2352.       "\n"
  2353.       "SINDATA ends\n"
  2354.       "END\n";
  2355.  
  2356.  
  2357.  main()
  2358.      {
  2359.      double dbPI  = 3.1415926536;
  2360.      double dbRad;
  2361.      FILE   * fp;
  2362.      int    iAngle;
  2363.      int    iSin;
  2364.  
  2365.      if (!(fp = fopen("sinedata.asm", "w")))
  2366.          {
  2367.          printf("Can't create sinedata.asm.\n");
  2368.          exit(1);
  2369.          }
  2370.  
  2371.      fprintf (fp, achFileHeader);
  2372.      fprintf (fp, "DW ");
  2373.  
  2374.      for (iAngle = 0; iAngle <= 90; iAngle++)
  2375.          {
  2376.          dbRad = (((double)iAngle) * dbPI) / 180.0;
  2377.          iSin = sin(dbRad) * 10000.0 + 0.5;
  2378.          fprintf(fp, " %5d", iSin);
  2379.  
  2380.          if (iAngle % 8 == 7)
  2381.              fprintf (fp, "\nDW ");
  2382.          else if (iAngle != 90)
  2383.              fprintf (fp, ",");
  2384.          }
  2385.  
  2386.      fprintf(fp, achFileFooter);
  2387.  
  2388.      fclose(fp);
  2389.      }
  2390.  
  2391.  
  2392.  Figure 16. SINEDATA.ASM, Generated by SINE.EXE
  2393.  
  2394.  ;
  2395.  ; Sine/Cosine Data Table
  2396.  ;
  2397.  ;
  2398.  ; Table of Sine values from 0 to 90 degrees
  2399.  ;
  2400.  SINDATA segment public
  2401.  DW      0,   175,   349,   523,   698,   872,  1045,  1219
  2402.  DW   1392,  1564,  1736,  1908,  2079,  2250,  2419,  2588
  2403.  DW   2756,  2924,  3090,  3256,  3420,  3584,  3746,  3907
  2404.  DW   4067,  4226,  4384,  4540,  4695,  4848,  5000,  5150
  2405.  DW   5299,  5446,  5592,  5736,  5878,  6018,  6157,  6293
  2406.  DW   6428,  6561,  6691,  6820,  6947,  7071,  7193,  7314
  2407.  DW   7431,  7547,  7660,  7771,  7880,  7986,  8090,  8192
  2408.  DW   8290,  8387,  8480,  8572,  8660,  8746,  8829,  8910
  2409.  DW   8988,  9063,  9135,  9205,  9272,  9336,  9397,  9455
  2410.  DW   9511,  9563,  9613,  9659,  9703,  9744,  9781,  9816
  2411.  DW   9848,  9877,  9903,  9925,  9945,  9962,  9976,  9986
  2412.  DW   9994,  9998, 10000
  2413.  SINDATA ends
  2414.  END
  2415.  
  2416.  
  2417.  
  2418.  Learning Windows Part IV: Integrating Controls and Dialog Boxes
  2419.  
  2420.  Marc Adler
  2421.  
  2422.  Dialog boxes and the control windows within them are an integral part of the
  2423.  Microsoft Windows graphical environment. The topic is broad enough to devote
  2424.  two articles, both the previous article in this "Learning Windows" series as
  2425.  well as this one, entirely to it. First, I conclude the discussion of
  2426.  controls with a look at combo boxes, scroll bars, and user-defined controls,
  2427.  then I place the controls into their proper setting: a dialog box. Finally,
  2428.  adding dialog boxes to the sample stock quoting application gives it most of
  2429.  its functionality.
  2430.  
  2431.  Combo Boxes
  2432.  
  2433.  The combo box, which was introduced in Windows1 Version 3.0, combines a
  2434.  single-line edit control and a list box into one control window. It can be
  2435.  used to enter a value in an edit field or to choose one string from a
  2436.  predefined list of text strings. A combo box can replace a series of radio
  2437.  buttons in a dialog box, taking up less space. In a word processing
  2438.  application, you might want to enter the type size, in points, of the font
  2439.  you will be using. A combo box could display a list of existing point sizes
  2440.  and permit you to type in another number. A radio button group could be used
  2441.  for this task, but if there were many point sizes, the radio button group
  2442.  would probably take too much room in the dialog box. In addition, the number
  2443.  of choices in a combo box can vary throughout the lifetime of an
  2444.  application, but a radio button group generally has a fixed number of
  2445.  choices. To add another choice to a combo box control, all the developer has
  2446.  to do is append a text string to a combo box's list box; adding another
  2447.  option to a radio group probably means creating a new radio button and
  2448.  rearranging the entire group.
  2449.  
  2450.  The first element of a combo box is a single-line edit control. A button
  2451.  control to the right of the edit field is the second element, and the third
  2452.  element is a list box control just below the edit control. As you scroll
  2453.  through the list box, the current selection is displayed in the edit
  2454.  control. A list box control in a combo box has the standard Windows 3.0 list
  2455.  box features, including possible owner-draw items.
  2456.  
  2457.  Windows 3.0 supports three styles of combo boxes. The simple combo box
  2458.  style, which is the least used, has the CBS_SIMPLE style flag. In a simple
  2459.  combo box, the list box is always visible. This kind of combo box has no
  2460.  advantage over defining a separate edit control and list box control, except
  2461.  that you don't have to code the list-box-to-edit tracking logic.
  2462.  
  2463.  The other two combo box styles are drop-down (see Figure 1) and drop-down
  2464.  list, defined by the CBS_DROPDOWN and the CBS_DROPDOWNLIST styles. The only
  2465.  difference between them is that the edit control is disabled in the
  2466.  drop-down list combo box, so users cannot enter their own values in the edit
  2467.  field. This is useful if you want to make users choose from a list of
  2468.  predefined values only.
  2469.  
  2470.  The nice thing about the drop-down and drop-down list styles is that the
  2471.  list box is hidden until you pull it down. You can pull it down either by
  2472.  clicking on the control's button or by pressing the Alt-Down key
  2473.  combination. You can use the Up, Down, Page Up and Page Down keys while the
  2474.  focus is on the control to scroll through the list box items.
  2475.  
  2476.  The messages and notification codes that are processed by the combo box
  2477.  message interface are similar to those used by the standard list box and
  2478.  edit controls. The only difference is that the prefixes CB_  or CBN_ are
  2479.  used instead of the EM_, EN_, LB_, and
  2480.  
  2481.  LBN_ prefixes. The single unique message, CB_SHOWDROPDOWN, is used to
  2482.  display or hide the list box portion of a drop-down or drop-down list combo
  2483.  box. Just before the list box portion is made visible, the CBN_DROPDOWN
  2484.  notification code is sent to the parent of the combo box. This gives the
  2485.  programmer a chance to modify the contents of the list box before it is
  2486.  shown to the user.
  2487.  
  2488.  Scroll Bars
  2489.  
  2490.  Many Windows applications use a window as a "viewport" into some sort of
  2491.  large data set. A list box window is a viewport into a series of strings, a
  2492.  word processing document window is a viewport into a portion of the data
  2493.  file, and so on.
  2494.  
  2495.  Scroll bars are the primary means for the user to inform the application
  2496.  that he or she wants to move the viewport to a new position in the data set.
  2497.  
  2498.  A scroll bar control can be either vertical or horizontal. It has two
  2499.  primary attributes: the range of values that the length of the scroll bar
  2500.  represents, and the current position in the range. The current position is
  2501.  indicated with a square icon called a thumb.
  2502.  
  2503.  Scroll bars are usually attached to a window when it is created. To attach a
  2504.  vertical scroll bar to a window, you simply give the window the WS_VSCROLL
  2505.  style during creation. Similarly, the WS_HSCROLL style attaches a horizontal
  2506.  scroll bar to the bottom of the window.
  2507.  
  2508.  Scroll bars can also be used as standalone control windows. A good example
  2509.  can be seen in the Edit Colors dialog box of Windows Paintbrush (see Figure
  2510.  2).
  2511.  
  2512.  A standalone scroll bar control is created in the same way as any other kind
  2513.  of control window--using CreateWindow or defining the control as part of a
  2514.  dialog box in a resource file. Vertical control scroll bars have the
  2515.  SBS_VERT style, and horizontal scroll bars have the SBS_HORZ style.
  2516.  
  2517.  Windows API functions allow you to query and set the current position and
  2518.  the range of the scroll bar. The five scroll bar functions are as follows:
  2519.  
  2520.  
  2521.  int GetScrollPos(hWnd, nBar)
  2522.  void GetScrollRange(hWnd, nBar, lpMin, lpMax)
  2523.  int SetScrollPos(hWnd, nBar, iPos, bRedraw)
  2524.  void SetScrollRange(hWnd, nBar, nMin, mMax, bRedraw)
  2525.  void ShowScroll bar(hWnd, nBar, bShow)
  2526.  
  2527.  
  2528.  The first parameter, hWnd, can be either the handle of a scroll bar (when
  2529.  dealing with a control scroll bar), or the handle of the window that
  2530.  contains the scroll bar. The second parameter, nBar, details which scroll
  2531.  bar you want. If nBar is SB_CTL, the hWnd parameter is the handle of a
  2532.  control scroll bar. If nBar is SB_VERT, hWnd is the handle of a window
  2533.  containing the vertical scroll bar; if nBar is SB_HORZ, hWnd is the handle
  2534.  of a window with a horizontal scroll bar.
  2535.  
  2536.  Scroll bars, unlike other control windows, do not generate WM_COMMAND
  2537.  messages when the parent window needs to be notified of some event. Instead,
  2538.  they generate WM_VSCROLL and WM_HSCROLL messages. WM_VSCROLL messages are
  2539.  sent by vertical scroll bars, and WM_HSCROLL messages are sent by horizontal
  2540.  scroll bars. The notification codes for these messages are the same, and are
  2541.  passed in wParam (see Figure 3). If the message was generated by a control
  2542.  scroll bar, the handle of the scroll bar is passed in the HIWORD of the
  2543.  lParam (otherwise, the HIWORD of lParam is 0). The LOWORD of the lParam
  2544.  contains the current thumb position for two of these notification messages.
  2545.  
  2546.  When the top arrow of a vertical scroll bar or the left arrow of a
  2547.  horizontal scroll bar is clicked, the SB_LINEUP notification code is sent to
  2548.  the parent window. Clicking on the down arrow or the right arrow generates
  2549.  an SB_LINEDOWN code. Clicking in the area between the top or left side of
  2550.  the scroll bar and the thumb causes the SB_PAGEUP code to be sent. And if
  2551.  you click between
  2552.  
  2553.  the bottom or right side of the scroll bar and the thumb, the SB_PAGEDOWN
  2554.  message is generated. As you drag the thumb, the scroll bar generates a
  2555.  series of SB_THUMBTRACK notification codes with the current position of the
  2556.  thumb in the LOWORD of lParam. The SB_THUMBTRACK notification codes allow
  2557.  the developer to vary the contents of the window continuously as the thumb
  2558.  is dragged. However, it is not always possible to update the contents of a
  2559.  window dynamically as the user is scrolling (this is often the case if the
  2560.  window's contents are too complex). When dragging stops, the scroll bar
  2561.  generates the SB_THUMBPOSITION message, which contains the final position of
  2562.  the thumb in the LOWORD of lParam. If dynamic scrolling of the window is not
  2563.  feasible, the window can be updated once at this point to reflect the new
  2564.  position of the viewport.
  2565.  
  2566.  When these notification codes are sent to the WinProc of the scroll bar's
  2567.  parent, the WinProc must update the scroll bar thumb to reflect the new
  2568.  position. The WinProc must scroll the data in the window as well. Windows
  2569.  automatically handles scrolling for list boxes, combo boxes, and edit
  2570.  controls that have scroll bars.
  2571.  
  2572.  Sample Scroll Bar Use
  2573.  
  2574.  Assume that you have written a simple Windows application that constantly
  2575.  generates a tone (like a program that aids guitarists in tuning their
  2576.  instruments). You attach a vertical scroll bar to the main window of the
  2577.  program; the range of the scroll bar represents eight octaves (there are 12
  2578.  notes in an octave, for a total of 96 notes), and the current position of
  2579.  the thumb represents the note that is currently sounded. (You have a
  2580.  function called SoundTone that accepts a note value and generates the
  2581.  corresponding tone.)
  2582.  
  2583.  First, create a window with a scroll bar and set the range of the scroll bar
  2584.  from 1 to 96. Also, the initial tone is set to an A-440Hz, the 22nd note in
  2585.  your range (see Figure 4).
  2586.  
  2587.  To get to the next or previous note in the range, the user must click on the
  2588.  down or up arrow of the scroll bar. To increase or decrease the octave, set
  2589.  up the scroll bar so that the user can click between the thumb and the down
  2590.  arrow, or the thumb and the up arrow. Dragging the thumb produces a sliding
  2591.  glissando sound (see Figure 5). The WM_VSCROLL messages that are sent to the
  2592.  main window must be processed and the tone adjusted accordingly. You must
  2593.  also reposition the thumb so that it ends up at the correct location in the
  2594.  scroll bar.
  2595.  
  2596.  Dialog Boxes
  2597.  
  2598.  Dialog boxes can be thought of as the glue that binds groups of control
  2599.  windows together. A dialog box is simply a pop-up window containing one or
  2600.  more child windows. These child windows can be in one of the previously
  2601.  discussed control classes, or they can be custom developed. Two
  2602.  characteristics distinguish a dialog box from an ordinary pop-up window.
  2603.  First, a dialog box can be defined in an external ASCII resource file.
  2604.  Second, a dialog box manager in Windows handles the interaction between the
  2605.  user and the dialog box.
  2606.  
  2607.  The dialog box manager processes the keystrokes that occur in a dialog box
  2608.  and performs the actions indicated by the keystroke. If the user presses
  2609.  Tab, the dialog box manager must search forward, starting after the control
  2610.  that currently has the focus, for the next control on which the focus can be
  2611.  set (that is, a control with the WS_TABSTOP style). If the user presses Esc,
  2612.  the dialog box manager generates a WM_COMMAND message with the IDCANCEL
  2613.  value and sends it to the user-defined dialog box procedure.
  2614.  
  2615.  Several steps are involved in using a dialog box in your Windows
  2616.  application. First, you must define a dialog box in the resource file (RC),
  2617.  compile it using the resource compiler, and attach it to the EXE file of
  2618.  your application. Second, your application must define a dialog box
  2619.  procedure to handle the messages sent to the dialog box. Third, your
  2620.  application must load the dialog box from the resource file and call the
  2621.  dialog box manager to display it so it can interact with the user.
  2622.  
  2623.  There are two types of dialog boxes, modal and modeless. Most applications
  2624.  use modal dialog boxes to gather information from the user. A modal dialog
  2625.  box does not permit you to interact with or switch to any other window in
  2626.  your application until you are finished using the dialog box. In fact, when
  2627.  you invoke a modal dialog box, the dialog box manager goes into its own
  2628.  message loop and dispatches only those messages meant for the dialog box or
  2629.  one of its controls. Usually a user gets out of a dialog box by clicking on
  2630.  an OK or Cancel push button or pressing Esc or Enter.
  2631.  
  2632.  Modeless dialog boxes allow you to switch the focus from them to the rest of
  2633.  the application. You switch the focus by clicking the mouse on another
  2634.  window. The Windows PIF Editor serves as a good example. In the PIF Editor,
  2635.  you can activate the pull-down menu while the focus is set to one of the
  2636.  modeless dialog box controls. Since the methods for loading and using
  2637.  modeless and modal dialog boxes are quite different, both will be discussed.
  2638.  
  2639.  Defining a Dialog Box in a Resource File
  2640.  
  2641.  Defining a dialog box and its controls by hand in a resource file is
  2642.  something that an experienced Windows programmer avoids, because it's much
  2643.  simpler and easier to use the Windows Dialog Editor (see Figure 6) supplied
  2644.  in the Microsoft Windows Software Development Kit (SDK). Dialog boxes and
  2645.  control windows can have many different options and styles. The Dialog
  2646.  Editor allows you to draw and edit a dialog box interactively and generate
  2647.  the necessary resource file definition.
  2648.  
  2649.  Figure 7 shows the syntax for a sample dialog box definition. The first line
  2650.  specifies the name, load options, memory options, and coordinates of the
  2651.  dialog box. The default load and memory options are LOADONCALL
  2652.  and MOVEABLE. A LOADONCALL resource is not loaded into memory until the
  2653.  application needs it. This is one of the primary advantages of resource
  2654.  files.
  2655.  
  2656.  The coordinates specified in dialog box definitions work in a special way.
  2657.  Because Windows applications should be as device-independent as possible,
  2658.  your dialog boxes should look the same no matter what sort of video display
  2659.  adapter is used. If a dialog box is meant to encompass the entire display,
  2660.  it should do so whether the video adapter is at 640 x 350 or 1024 x 768
  2661.  resolution. To ensure this, the dialog box coordinate system is based upon
  2662.  the dialog base unit. This is a unit of measurement that is calculated based
  2663.  on the current system font. In a dialog box definition, the x coordinate and
  2664.  the width are measured in 1/8 of a dialog base unit. The y coordinate and
  2665.  the height are 1/4 of a dialog base unit. To determine the exact number of
  2666.  pixels in the horizontal and vertical dialog base units, use the new Windows
  2667.  3.0 function GetDialogBaseUnits.
  2668.  
  2669.  After you define the name, load options, memory options, and coordinates of
  2670.  the dialog box, you can define other dialog box options. The STYLE statement
  2671.  can be used to give the dialog box certain style attributes, the same kind
  2672.  that are used in the CreateWindow function. Four styles are specific to
  2673.  dialog boxes. These styles are DS_LOCALEDIT, DS_MODALFRAME,
  2674.  DS_NOIDLEMSG, and DS_SYSMODAL. The DS_SYSMODAL style creates a system
  2675.  modal dialog box, in which all windows in the system are disabled until you
  2676.  exit the dialog box. This style of dialog box could be used, for example, to
  2677.  signal a fatal error that would affect the performance of the Windows
  2678.  session.
  2679.  
  2680.  You can give the dialog box a title with the CAPTION statement. Other
  2681.  options are the MENU statement, the CLASS statement, and the FONT statement.
  2682.  The FONT statement, new to Windows 3.0, allows you to specify the type style
  2683.  and point size of the text inside the dialog box.
  2684.  
  2685.  Once this header information has been defined in the resource definition,
  2686.  all you need to do is define a list of the control windows between the BEGIN
  2687.  and END statements. However, the Windows SDK Dialog Editor does all of this
  2688.  work for you, allowing you to assemble controls graphically, while it builds
  2689.  the actual RC file of definitions.
  2690.  
  2691.  The definition of the Add Tick dialog box (see Figure 18) for the stock
  2692.  charting application is shown in Figure 7. This definition was generated by
  2693.  the Dialog Editor. You can usually tell if a dialog box definition was
  2694.  generated by the Dialog Editor because it generates the longer CONTROL-style
  2695.  statements to define controls as opposed to the handwritten format.
  2696.  
  2697.  Tab Stops and Groups
  2698.  
  2699.  I previously stated that the dialog box manager handles keystrokes within a
  2700.  dialog box, and that when Tab is pressed, the dialog box manager moves the
  2701.  input focus to the next control. How does it know which control to move the
  2702.  input focus to?
  2703.  
  2704.  The WS_TABSTOP style bit must be set for every control window to which you
  2705.  can move by pressing Tab or BackTab. This is especially important for
  2706.  programmer-defined controls, since the dialog box manager has no other way
  2707.  of knowing if a custom control is used for output only or can interact with
  2708.  the user. (If a static control has the WS_TABSTOP style set, the dialog box
  2709.  manager must search for the next nonstatic control after the static control
  2710.  in order to set the input focus to a valid control.)
  2711.  
  2712.  The dialog box manager also supports something called a control group. This
  2713.  is a group of controls organized so that you can move among them by pressing
  2714.  the arrow keys instead of Tab and BackTab. The most common example is a
  2715.  group box that contains radio buttons. If the first radio button in the
  2716.  group box has the WS_GROUP style and the first control defined outside of
  2717.  the group also has the WS_GROUP style, you will be able to move among the
  2718.  radio buttons by using the arrow keys. If you want to move to a control that
  2719.  resides outside of the radio group, you must use Tab. In general, to create
  2720.  a control group, give the first control in the group the WS_GROUP style and
  2721.  give the first control defined outside of the group the WS_GROUP style too.
  2722.  
  2723.   If you want to find the window handle of the next item in a control group,
  2724.  you can use the GetNextDlgGroupItem function. This function is useful to set
  2725.  up custom keyboard handling in a dialog box. For example, if you have a
  2726.  Windows application that offers customized keyboard mapping, the user might
  2727.  define a key combination to be the same as the down arrow key (for instance,
  2728.  Ctrl-X in WordStar). When the user presses Ctrl-X in a dialog box, you may
  2729.  want to set the input focus to the next member in a control group. You can
  2730.  use the GetNextDlgGroupItem function to get the window handle of the next
  2731.  control in the group and set the focus to it. A similar function,
  2732.  GetNextDlgTabItem, is used for controls with the WS_TABSTOP style.
  2733.  
  2734.  Loading a Dialog Box
  2735.  
  2736.  Now that the dialog box has been defined, compiled, and attached to the EXE
  2737.  file, you want to load it into your application. Four functions allow you to
  2738.  load or create a modal dialog box and have it processed by the dialog manager
  2739.  of the functions, DialogBox and DialogBoxParam, load the dialog box from the
  2740.  RES file that is attached to the EXE. The other functions, DialogBoxIndirect
  2741.  and DialogBoxIndirectParam, create dialog boxes from a description stored in
  2742.  a template in your application. Since this method is uncommon, I will not
  2743.  discuss it.
  2744.  
  2745.  The function used to load and invoke a dialog box is DialogBox.
  2746.  
  2747.  FARPROC lpfn;
  2748.  lpfn = MakeProcInstance((FARPROC) OpenDlg,
  2749.                          hThisInstance);
  2750.  DialogBox(hThisInstance, "Open", hWnd, lpfn);
  2751.  FreeProcInstance(lpfn);
  2752.  
  2753.  The MakeProcInstance call takes the address of the programmer-defined dialog
  2754.  box procedure and creates a small piece of code known as a thunk. A thunk
  2755.  first binds the data specified by hThisInstance to the function pointed to
  2756.  by OpenDlg, and then branches to that function. MakeProcInstance returns a
  2757.  far address to the thunk. When finished with the dialog box invocation, free
  2758.  the thunk by calling FreeProcInstance. (Thunks are a complex mechanism and
  2759.  are described in greater detail in Programming Windows,  Microsoft Press,
  2760.  1990)
  2761.  
  2762.  The DialogBox function takes four arguments; the handle of the application's
  2763.  instance (remember, this is a unique program identifier so that Windows can
  2764.  distinguish multiple copies of the same application that are running
  2765.  simultaneously); the name of the dialog box to load; the handle of the owner
  2766.  window; and the pointer to the instance thunk. The value returned by
  2767.  DialogBox is the value that the programmer-defined dialog box procedure
  2768.  returns. (Actually, the return value is the value that is passed as the
  2769.  second argument to the EndDialog function.)
  2770.  
  2771.  Dialog Box Procedure
  2772.  
  2773.  Every dialog box must have a programmer-defined dialog box procedure
  2774.  associated with it. The dialog box procedure is similar to a standard
  2775.  WinProc, but dialog box procedures and WinProcs differ slightly. First, a
  2776.  dialog box procedure returns a Boolean value, TRUE or FALSE.
  2777.  Unlike a standard WinProc, your dialog box procedure must return TRUE if
  2778.  it processes a message and FALSE if it does not.
  2779.  
  2780.  When you invoke a modal dialog box, the dialog box manager goes into its own
  2781.  internal message loop. If it receives a message intended for the dialog box,
  2782.  it passes the message to your dialog box procedure first. If the dialog box
  2783.  procedure returns FALSE, the dialog box manager passes the message to its
  2784.  own internal default dialog box procedure. If your dialog box procedure
  2785.  returns TRUE, the dialog box manager does nothing further with the message
  2786.  and reads the next message. Windows 3.0 allows the advanced programmer to
  2787.  access the default dialog box procedure, DefDlgProc. Microsoft distributes
  2788.  the source code to DefDlgProc with the Windows 3.0 SDK.
  2789.  
  2790.  The dialog box procedure should be defined as follows:
  2791.  
  2792.  BOOL FAR PASCAL MyDialogProc(hDlg, message,
  2793.                               wParam, lParam)
  2794.   HWND  hDlg;
  2795.   WORD  message;
  2796.   WORD  wParam;
  2797.   DWORD lParam;
  2798.  
  2799.  This header looks exactly like a WinProc, except for the BOOL return value.
  2800.  And, just like a WinProc, the dialog box procedure must be exported in your
  2801.  application's DEF file.
  2802.  
  2803.   EXPORTS
  2804.      .
  2805.      .
  2806.      .
  2807.    MYDialogProc
  2808.  
  2809.  If a dialog box does not seem to be behaving correctly, check that you have
  2810.  exported the dialog box function. This is a common error among novice
  2811.  Windows programmers (and experienced ones too).
  2812.  
  2813.  Most simple dialog box procedures process only the WM_INITDIALOG and
  2814.  WM_COMMAND messages. WM_INITDIALOG, similar to the WM_CREATE message, is
  2815.  sent by the dialog box manager just before the dialog box is displayed for
  2816.  the first time. This message gives the dialog box procedure the opportunity
  2817.  to initialize the contents of the controls in the dialog box. The dialog box
  2818.  procedure, for example, could fill in the contents of edit fields with
  2819.  default values, fill list boxes with strings, and check certain radio
  2820.  buttons and check boxes. If you do not want the dialog box manager to set
  2821.  the initial focus to the first control with the WS_TABSTOP style
  2822.  automatically, you can set the focus to another control at this point. If
  2823.  you do change the focus, the dialog box procedure must return FALSE after
  2824.  processing the WM_INITDIALOG message. If it returns TRUE, the dialog box
  2825.  manager will set the focus automatically. (This message is the exception to
  2826.  the rule stated earlier about the Boolean values returned by your dialog box
  2827.  procedure.)
  2828.  
  2829.  The lParam of the WM_INITDIALOG message was not used in previous versions of
  2830.  Windows. Windows 3.0 allows you to pass the dialog box manager a long value
  2831.  that the dialog box manager will in turn pass to your dialog box procedure
  2832.  as the lParam value of the WM_INITDIALOG message. To do this, use the
  2833.  DialogBoxParam function. This function is identical to DialogBox, except
  2834.  that a fifth argument is added, the long value to pass in the WM_INITDIALOG
  2835.  message.
  2836.  
  2837.  Other than processing the WM_INITDIALOG message, your dialog box procedure
  2838.  mainly handles the WM_COMMAND messages generated when the user manipulates a
  2839.  control window. These WM_COMMAND messages usually come in two varieties: the
  2840.  ones generated when you click on a button control, and the ones sent with
  2841.  notification codes in the HIWORD of lParam.
  2842.  
  2843.  When the user clicks the OK or Cancel button, it usually means the user
  2844.  wants to dismiss the dialog box. A WM_COMMAND message is sent to the dialog
  2845.  box procedure with wParam set to the control identifier of the push
  2846.  button. The dialog box manager is informed that the user is through with the
  2847.  dialog box by a call to EndDialog.
  2848.  
  2849.  EndDialog takes two arguments, the handle of the dialog box, and a
  2850.  word-length value. This value will be used as the return value from the call
  2851.  to DialogBox.
  2852.  
  2853.  switch (message)
  2854.  {
  2855.      case WM_COMMAND :
  2856.      switch (wParam)
  2857.      {
  2858.          case IDOK :
  2859.                           EndDialog(hDlg, TRUE);
  2860.          break;
  2861.          case IDCANCEL :
  2862.                           EndDialog(hDlg, FALSE);
  2863.          break;
  2864.  
  2865.  
  2866.  If the user pressed  OK, EndDialog is called to tell the dialog box manager
  2867.  that the dialog box should be dismissed. The value TRUE will be returned by
  2868.  the dialog box manager to the application. In the following statement the
  2869.  value TRUE would be returned and assigned to the variable rc.
  2870.  
  2871.  rc = DialogBox(hInstance, "AddTick", hWndMain,
  2872.                 lpfnDialogProc);
  2873.  
  2874.  Similarly, if the user clicked on the Cancel push button, the value False
  2875.  would be returned to the application.
  2876.  
  2877.  EndDialog does not dismiss the dialog box immediately. It sets a bit in a
  2878.  private data structure that the dialog box manager allocates for each dialog
  2879.  box. (It also copies the value of the second argument into this structure.)
  2880.  When your dialog box procedure returns control to the dialog box manager,
  2881.  the manager examines this bit, and if it is set, the manager breaks out of
  2882.  its message loop.
  2883.  
  2884.  Message Boxes
  2885.  
  2886.  Windows programs commonly inform the user of a potential or existing error
  2887.  condition. For instance, if the user attempts to exit a word processing
  2888.  application without first saving the document, the application should bring
  2889.  up a dialog box that contains a warning message and a choice of actions. The
  2890.  dialog box should have a message such as "Save the document before exiting?"
  2891.  as well as three push buttons labeled Yes, No, and Cancel.
  2892.  
  2893.  It would be extremely tedious if the Windows programmer had to design dialog
  2894.  boxes for each possible error or warning condition. Fortunately, you can
  2895.  call a single function to display some text in a dialog box and attach one
  2896.  or more predefined buttons to it. This kind of dialog box is called a
  2897.  message box; not surprisingly, the function that creates and displays one is
  2898.  called MessageBox.
  2899.  
  2900.  The nice thing about using message boxes is that Windows itself contains the
  2901.  dialog box procedure for all message boxes and handles all the WM_COMMAND
  2902.  messages. You don't have to create your own dialog box procedure, export it,
  2903.  and load the dialog box. All you have to do is process the return code from
  2904.  MessageBox.
  2905.  
  2906.  The syntax of the MessageBox function is shown below.
  2907.  
  2908.  int MessageBox(HWND hWndParent, LPSTR lpText,
  2909.                 LPSTR lpCaption, WORD wType)
  2910.  
  2911.  The first parameter is the handle of the owner window. The second parameter
  2912.  is the static text that will appear inside the message box. If it is too
  2913.  long to fit on one line, Windows wraps the text to the next line and
  2914.  increases the height of the message box accordingly. The third parameter is
  2915.  an optional title that will appear in the caption of the message box. The
  2916.  fourth parameter consists of bit flags that tell Windows what kinds of
  2917.  buttons and icons you want put into the message box. If you want a push
  2918.  button other than the predefined push buttons, you must define your own
  2919.  dialog box to be used as a message box. Windows gives you Yes, No, Cancel,
  2920.  Abort, Ignore, and Retry push buttons. It also allows you to place one of
  2921.  several predefined system icons on the left side of the message box (see
  2922.  Figure 8).
  2923.  
  2924.  The value returned from MessageBox is the control identifier of the push
  2925.  button the user selected. (If the user presses OK, the IDOK value is
  2926.  returned; if the user presses No, the IDNO value is returned, and so on.)
  2927.  The complete list of identifiers of these buttons can be found in WINDOWS.H
  2928.  (see Figure 9).
  2929.  
  2930.  A typical use of a message box is:
  2931.  
  2932.  rc=MessageBox(hWndMain,
  2933.                "Do you want to exit the application?",
  2934.                "Exit",MB_YESNO | MB_ICONQUESTION);
  2935.       if (rc == IDYES)
  2936.               PostQuitMessage(0);
  2937.  
  2938.  Modeless Dialog Boxes Revisited
  2939.  
  2940.  Modeless dialog boxes can be thought of as a hybrid of normal overlapped
  2941.  windows and modal dialog boxes. To create a modeless dialog box, use the
  2942.  CreateDialog function. The syntax of CreateDialog is exactly the same as the
  2943.  DialogBox function. However, when you create a modeless dialog box, the
  2944.  CreateDialog function returns immediately with the handle of the dialog box;
  2945.  Windows does not invoke the internal dialog box manager. A typical call to
  2946.  create a modeless dialog box is shown below.
  2947.  
  2948.  hDlgModeless=CreateDialog(hInstance,"MyModelessDlg",
  2949.                            hWndMain, lpfnProc);
  2950.  
  2951.  Since a modeless dialog box is simply a standard overlapped window with
  2952.  child windows, messages get sent to it as they do to any other window in
  2953.  your application. All you need is your application's normal message loop:
  2954.  
  2955.  while (GetMessage(&msg, 0, 0, 0))
  2956.  {
  2957.   TranslateMessage(&msg);
  2958.   DispatchMessage(&msg);
  2959.  }
  2960.  
  2961.  But there is something missing. Suppose the input focus is set to one of the
  2962.  controls in the modeless dialog box and you press Tab. Nothing happens,
  2963.  because there is no dialog box manager that processes the keystrokes and
  2964.  gives a special significance to the Tab key. Of course, you could put code
  2965.  in your application to handle Tab, but it would be a lot easier to "call the
  2966.  dialog box manager" temporarily.
  2967.  
  2968.  Windows allows you to do this. The function
  2969.  
  2970.  IsDialogMessage(hDlg, &msg)
  2971.  
  2972.  tests to see if the message contained in the passed MSG structure is meant
  2973.  for the dialog box whose handle is passed in the first argument. If so,
  2974.  IsDialogMessage performs all of the necessary keystroke translations and
  2975.  dispatches the message itself to the dialog box. It takes care of all of the
  2976.  keystroke interpretations. It then returns TRUE if the message has been
  2977.  processed, and FALSE if not. The standard message loop modified to include
  2978.  the use of IsDialogMessage looks like this:
  2979.  
  2980.      while (GetMessage(&msg, 0, 0, 0))
  2981.      {
  2982.       if (!hDlgModeless ||
  2983.             !IsDialogMessage(hDlgModeless, &msg))
  2984.       {
  2985.        TranslateMessage(&msg);
  2986.        DispatchMessage(&msg);
  2987.       }
  2988.      }
  2989.  
  2990.  After obtaining the message, the message is passed to IsDialogMessage. If it
  2991.  returns TRUE, you know it processed the message fully and there is no reason
  2992.  to call TranslateMessage and DispatchMessage.
  2993.  
  2994.  The only other concern with modeless dialog boxes is that since the dialog
  2995.  manager is not used with modeless dialog boxes, the EndDialog function
  2996.  cannot be used to terminate dialog box processing and destroy the dialog
  2997.  box. Instead, you must use the standard DestroyWindow call. You also have to
  2998.  invalidate the dialog box handle so that the IsDialogMessage function won't
  2999.  be called in your message loop.
  3000.  
  3001.  DestroyWindow(hDlg);
  3002.  hDlgModeless = 0;
  3003.  
  3004.  Sample Application
  3005.  
  3006.  At the end of the previous article in this series, the stock charting
  3007.  application had a complete pull-down menu system and  MDI capabilities.
  3008.  Every time you chose the File/New menu item, a new MDI child window was
  3009.  created. Items on the Window pull-down menu automatically tiled and cascaded
  3010.  the child windows, and arranged minimized windows nicely. But that was all
  3011.  the application did; there was no code actually relating to stocks.
  3012.  
  3013.  Now that you understand dialog boxes, an entry form will be implemented that
  3014.  prompts the user for information about the stocks. You'll need to decide
  3015.  what kind of information should be associated with each stock, and create a
  3016.  data structure describing a stock object. The stock information will also
  3017.  need to be stored for easy access. Since this application is not designed to
  3018.  be a commercial application (and since this application is designed to teach
  3019.  Windows programming, not data structures), ease of implementation and
  3020.  comprehension will be favored over performance. When applicable, I will
  3021.  discuss alternatives you can use to obtain more palatable results.
  3022.  
  3023.  The modules to build the version of STOCK.EXE discussed here are shown in
  3024.  Figure 10. A real-time stock charting system is not being created, so the
  3025.  application will not continuously monitor a stock's performance as it is
  3026.  traded throughout the business day. The only concern is the stock's closing
  3027.  price and perhaps its volume. Other types of analyses might involve the
  3028.  average price during the day or the high and low prices, but that is left as
  3029.  an exercise to the reader.
  3030.  
  3031.  You may also want to keep track of each date. Here's a structure to hold the
  3032.  date:
  3033.  
  3034.  typedef struct tagDate
  3035.  {
  3036.   BYTE  chMonth;
  3037.   BYTE  chDay;
  3038.   BYTE  chYear;
  3039.  } DATE;
  3040.  
  3041.  Six bytes are used for each date; however, I could probably get away with
  3042.  two bytes if I stored the date as an offset from January 1, 1900 and put it
  3043.  in an unsigned int. Some analysis programs do not store the date along with
  3044.  every tick; they are just concerned with a series of price and volume data.
  3045.  
  3046.  To avoid the overhead of floating point arithmetic, you can store the price
  3047.  as a four-byte value using the following formula.
  3048.  
  3049.  price = (dollar amount) X (fraction denominator) + (fractional amount)
  3050.  
  3051.  For instance, a price of 16 7/8 would be stored as (16 * 8) + 7 = 135.
  3052.  
  3053.  Now you can define a structure that holds the daily trade information:
  3054.  
  3055.  typedef DWORD  PRICE;
  3056.  typedef DWORD  VOLUME;
  3057.  
  3058.  typedef struct tagTick
  3059.  {
  3060.   PRICE  price;
  3061.   VOLUME dwVolume;
  3062.   DATE   date;
  3063.  } TICK, FAR *LPTICK;
  3064.  
  3065.  You also need a data structure that describes the properties of a stock
  3066.  graph (see Figure 11). A future article will delve further into the logic of
  3067.  creating graphs. This data structure will be discussed more fully at that
  3068.  time.
  3069.  
  3070.  All this information must be transferable to and from disk, or the program
  3071.  would be pretty useless. Besides the ticker information, you want to save
  3072.  the name of the stock (an up-to-five letter symbol), a description of the
  3073.  stock, the graphing parameters, and the number of ticks. You also want to
  3074.  store a signature at the beginning of the file for data integrity to ensure
  3075.  that the file read from disk really is one of the stock files (see Figure
  3076.  12).
  3077.  
  3078.  Finally, a structure is needed to keep track of all of the stock information
  3079.  while the application is running. In addition to all of the information that
  3080.  resides in the stock file, you need to store the filename of the stock file,
  3081.  the handle of the MDI child window in which the stock information is
  3082.  displayed, a handle to the memory buffer that holds the tick data, and some
  3083.  state flags that record the current status of the stock. All the stocks are
  3084.  linked together in a single-linked list; a handle to the next stock
  3085.  information data structure is kept in each structure (see Figure 13).
  3086.  
  3087.  Each stock is stored in its own separate file whose name is comprised of the
  3088.  stock symbol and an STO extension. For a commercial application, you would
  3089.  most likely use a Windows-compatible commercial database access library.
  3090.  
  3091.  Using Dialog Boxes
  3092.  
  3093.  To demonstrate dialog boxes, examine a dialog box in the stock-charting
  3094.  application. When the user chooses the File Open menu item, a dialog box
  3095.  should be displayed that lists the files with the STO extension found in the
  3096.  current directory. The user should be allowed to change directories and disk
  3097.  drives to search for other STO files. Almost all Windows applications have a
  3098.  File Open dialog box. Unfortunately, Windows provides no standard File Open
  3099.  dialog box, so each vendor implements his or her own in a slightly different
  3100.  way. The stock-charting application makes use of some sample code that comes
  3101.  with the Windows SDK for a typical File Open dialog box.
  3102.  
  3103.  You must first define the dialog box in the resource file. The resource
  3104.  definition is shown in Figure 14.
  3105.  
  3106.  The dialog box's name is Open; it has a caption and a system menu (see
  3107.  Figure 15). An edit field lets the user input a filename or specification
  3108.  other than the default *.STO. The edit field also tracks the current
  3109.  selection in the file list box, which contains the names of all the files in
  3110.  the current directory matching the file specification. It also contains a
  3111.  list of subdirectories in the current directory, and a list of all disk
  3112.  drives on the system. Finally, there is an OK push button that the user can
  3113.  click to open the file, and a CANCEL button that aborts the Open operation.
  3114.  The OK push button is the default, so if the user presses Enter, a
  3115.  WM_COMMAND message with IDOK in wParam is sent to the user-defined dialog
  3116.  box procedure.
  3117.  
  3118.  The WinProc of the main window in the application processes the WM_COMMAND
  3119.  message, and when wParam is set to ID_OPEN, invokes the File Open dialog
  3120.  box.
  3121.  
  3122.   case ID_OPEN :
  3123.  lpfn = MakeProcInstance((FARPROC) OpenDlg,
  3124.                               hThisInstance);
  3125.  DialogBox(hThisInstance, "Open", hWnd, lpfn);
  3126.  FreeProcInstance(lpfn);
  3127.  break;
  3128.  
  3129.  You want the dialog box procedure to return a handle to the opened stock
  3130.  file rather than a TRUE/FALSE value or a control window identifier. A file
  3131.  handle returned by the Windows OpenFile function is a WORD value, so it can
  3132.  be returned to the application by passing it as the second argument to
  3133.  EndDialog.
  3134.  
  3135.  Here is the heading of the dialog box procedure:
  3136.  
  3137.  BOOL FAR PASCAL OpenDlg(hDlg, message,
  3138.                          wParam, lParam)
  3139.   HWND hDlg;
  3140.   unsigned message;
  3141.   WORD wParam;
  3142.   LONG lParam;
  3143.  {
  3144.   WORD index;
  3145.   PSTR pTptr;
  3146.   HANDLE hFile;
  3147.  
  3148.   switch (message)
  3149.   {
  3150.     .
  3151.     .
  3152.     .
  3153.  
  3154.  The only two messages that you need to process in the dialog box procedure
  3155.  are the WM_INITDIALOG and the WM_COMMAND messages. First, look at the small
  3156.  function called UpdateListBox below. This function forms a complete pathname
  3157.  with a file specification at the end by concatenating two string variables,
  3158.  one holding the current path and the other holding the current filespec. It
  3159.  then calls a Windows function called DlgDirList.
  3160.  
  3161.  DlgDirList takes five arguments: a handle to a dialog
  3162.  box, the identifier of a file list box within that dialog box, the
  3163.  identifier of a static text field also within that dialog box, a string
  3164.  containing a path and file specification, and a value representing file
  3165.  attributes. It then searches the path for all files matching the file
  3166.  specification and the desired attributes. The name of each matching file is
  3167.  placed into the list box, and the full pathname is placed into the static
  3168.  text field. DlgDirList also places the subdirectory and drive names in the
  3169.  list box automatically.
  3170.  
  3171.  Finally, UpdateListBox sets the contents of the dialog box's edit field to
  3172.  the default file specification by calling SetDlgItemText. This function
  3173.  takes the identifier of a control window in a dialog box, determines the
  3174.  window handle of the control, and sends a WM_SETTEXT message to it.
  3175.  
  3176.  void UpdateListBox(hDlg)
  3177.   HWND hDlg;
  3178.   {
  3179.    strcpy(str, DefPath);
  3180.    strcat(str, DefSpec);
  3181.     DlgDirList(hDlg, str, IDC_LISTBOX, IDC_PATH, 0x4010);
  3182.    SetDlgItemText(hDlg, IDC_EDIT, DefSpec);
  3183.   }
  3184.  
  3185.  Next the WM_INITDIALOG message is processed. A call to UpdateListBox fills
  3186.  the list box with the names of the STO files and sets the contents of the
  3187.  file specification edit control to *.STO. The EM_SETSEL message is then sent
  3188.  to the edit control to highlight the entire field. If you want to send a
  3189.  message to any control within a dialog box, and you only know the identifier
  3190.  of the control, you can use SendDlgItemMessage. This is equivalent to the
  3191.  following statement.
  3192.  
  3193.   SendMessage(GetDlgItem(hDlg, ID_CONTROL), msg,
  3194.               wParam, lParam);
  3195.  
  3196.  You also set the initial focus to the edit control.
  3197.  
  3198.  case WM_INITDIALOG:      /* message: initialize    */
  3199.   UpdateListBox(hDlg);
  3200.   SendDlgItemMessage(hDlg,/* dialog handle          */
  3201.     IDC_EDIT,               /* where to send message  */
  3202.     EM_SETSEL,            /* select characters      */
  3203.     NULL,               /* additional information */
  3204.     MAKELONG(0, 0x7fff)); /* entire contents        */
  3205.   SetFocus(GetDlgItem(hDlg, IDC_EDIT));
  3206.   return (FALSE);
  3207.          /* Indicates the focus is set to a control */
  3208.  
  3209.  Now it's time to process the WM_COMMAND message. You want the edit control
  3210.  to track the currently selected item in the list box. When the current
  3211.  selection of a list box changes, the list box notifies the dialog box by
  3212.  sending a WM_COMMAND message with the identifier of the list box in wParam
  3213.  and the LBN_SELCHANGE notification code in the HIWORD of lParam (see Figure
  3214.  16). An easy way to get the text of the currently selected item is to call
  3215.  the built-in Windows function DlgDirSelect.
  3216.  
  3217.  Why aren't LB_GETCURSEL and LB_GETTEXT messages sent to the list box? Other
  3218.  than the fact that I have to write more code to do this, the DlgDirSelect
  3219.  function removes the square brackets surrounding the name of a directory and
  3220.  the square brackets and hyphens surrounding the names of disk drives in the
  3221.  list box. It returns zero if the selected item was a filename and nonzero if
  3222.  it was a directory name. In this case, if the currently selected item is a
  3223.  filename, you fill the edit control with the name of that file, and if it is
  3224.  a directory name, you refresh the list box.
  3225.  
  3226.  Double-clicking a list box selection has the same result as single-clicking
  3227.  a list box item and then clicking OK. When the LBN_DLBCLK notification code
  3228.  for the list box is received, the program simply jumps to the code that is
  3229.  responsible for opening the file (see Figure 17).
  3230.  
  3231.  Finally, you must process the two push buttons in the dialog box. Remember,
  3232.  when a button is pressed, a WM_COMMAND message is sent to the parent window
  3233.  with wParam set to the control identifier of the button. If the user clicks
  3234.  Cancel, you simply end the dialog box processing and return NULL for the
  3235.  value of the file handle. If the user clicks OK, you have a lot more work to
  3236.  do. First, examine the contents of the edit field and see if the filename
  3237.  contains a wildcard. If it does, assume that the user wants to search for a
  3238.  new filespec. You get the file specification, separate it into a pathname
  3239.  and a filespec, and refill the file directory list box with files matching
  3240.  the new spec. If the edit field is empty, put up a message box warning the
  3241.  user and return control to the dialog box manager. If neither is the case,
  3242.  you have a valid filename in the edit field: open the file and return the
  3243.  file handle.
  3244.  
  3245.  There you have it--your first dialog box. Once you code a few dialog boxes,
  3246.  you will find yourself churning out dialog box code by rote.
  3247.  
  3248.  Adding a Tick
  3249.  
  3250.  After a day of trading activity, you need to input the closing price and the
  3251.  total trading volume for each stock in the database. To do this, select the
  3252.  Add Tick item from the Edit menu. The Add Tick dialog box is displayed
  3253.  forthe current stock (see Figure 18); you can fill in the various fields to
  3254.  append a single tick to the list of tickers for that stock. The Add Tick
  3255.  dialog has three fields; the trading date, the volume, and the final price
  3256.  (the final price must be an integer for now). At this intermediate stage of
  3257.  the application, the date and the volume fields are basically ignored in the
  3258.  code--only the final price is important. Also, at this time, a tick cannot
  3259.  be inserted in the middle of the ticker list. This will be remedied in a
  3260.  future version of the code.
  3261.  
  3262.  Options Dialog Box
  3263.  
  3264.  The characteristics of a stock and how its graph is displayed can be
  3265.  controlled through the Options dialog box (see Figure 19). Actually, the
  3266.  same dialog box is displayed when you add a new stock to the database and
  3267.  when you change the properties of an existing stock. The only difference is
  3268.  that in the latter case, the field that contains the name of the stock is
  3269.  disabled; this is the only piece of information that cannot be altered in an
  3270.  existing stock. The following lines of code disable the symbol control if we
  3271.  are altering the characteristics of an existing stock.
  3272.  
  3273.  if (lpStockInfo->hTicks != NULL)
  3274.      .
  3275.      .
  3276.      .
  3277.  EnableWindow(GetDlgItem(hDlg, ID_SYMBOL), FALSE);
  3278.  
  3279.  As mentioned above, at this stage of the program, some of the fields in this
  3280.  dialog box are ignored by the code. The minimum and maximum prices define
  3281.  the range of the y-axis of the stock graph. The scale factor is a number
  3282.  that all of the numeric data is divided by before the number is plotted on
  3283.  the graph. If the price range of a stock is from 100 to 300, a ticker price
  3284.  can be 201 different values (assuming that each price is a whole number).
  3285.  Instead of having the y-axis of the graph divided into 201 points, you can
  3286.  have the y-axis represent 20 different points if you divide each price by a
  3287.  scale factor of 10. The tick interval is the interval between two successive
  3288.  tick marks on the y-axis. The price denominator field is ignored in this
  3289.  release. It represents the denominator in the fractional part of the stock
  3290.  price. (Most stocks are traded in either eighths or sixteenths.) The final
  3291.  component of the Options dialog box is the owner-draw combo box that
  3292.  contains the styles for the various pens that draw the horizontal and
  3293.  vertical grids of the graph.
  3294.  
  3295.  Trying It Out
  3296.  
  3297.  The function StockFileRead reads a stock file into the application. You can
  3298.  test this application using the stock file included with the application.
  3299.  StockFileRead first allocates space for the stock header and then reads the
  3300.  header. The number field of the header is examined to ensure that it is a
  3301.  valid stock file. You allocate memory to hold all of the ticker information
  3302.  and then read the tickers. Then, call GraphCreateWindow to create an MDI
  3303.  child window for this stock. The handle of the MDI child is returned and
  3304.  stored in the stock information structure. Likewise, the memory handle of
  3305.  the stock information structure is stored in the "extra-bytes" area in the
  3306.  window by using the SetWindowWord function. This makes it possible to
  3307.  determine stock information associated with a given window very quickly.
  3308.  
  3309.  When a stock window is displayed, some text about the stock is also
  3310.  displayed. A future installment will discuss graphics in Windows. I will
  3311.  then expand this application to show graphical information about each stock.
  3312.  
  3313.  This article focused on dialog boxes, which are often the most important
  3314.  objects in a Windows application. You saw how to define one in a resource
  3315.  file, load it into an application, and create a user-defined dialog box
  3316.  procedure to handle the messages. The next article finishes the discussion
  3317.  of dialog boxes, and discusses some of  the graphical capabilities of
  3318.  Windows.
  3319.  
  3320.  1  As used herein, "Windows" refers to the Microsoft Windows graphical
  3321.  environment. Windows refers only to this Microsoft product and is not
  3322.  intended to refer to such products generally.
  3323.  
  3324.  Figure 7. Definition of the Add Tick Dialog Box
  3325.  
  3326.  
  3327.  ADDTICK DIALOG LOADONCALL MOVEABLE DISCARDABLE 112, 31, 106, 86
  3328.   CAPTION "Add a Tick"
  3329.   STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_POPUP
  3330.  BEGIN
  3331.    CONTROL "Date:", -1, "static", SS_LEFT | WS_CHILD, 2, 7, 22, 8
  3332.    CONTROL "", ID_TICK_DATE, "edit", ES_LEFT | WS_BORDER | WS_TABSTOP |
  3333.    WS_CHILD, 53, 5, 48, 12
  3334.    CONTROL "Closing price:", -1, "static", SS_LEFT | WS_CHILD, 2, 26, 55, 11
  3335.    CONTROL "", ID_TICK_PRICE, "edit", ES_LEFT | WS_BORDER | WS_TABSTOP |
  3336.    WS_CHILD, 57, 25, 44, 12
  3337.    CONTROL "Volume:", -1, "static", SS_LEFT | WS_CHILD, 2, 44, 32, 8
  3338.    CONTROL "", ID_TICK_VOLUME, "edit", ES_LEFT | WS_BORDER | WS_TABSTOP |
  3339.    WS_CHILD, 38, 43, 63, 12
  3340.    CONTROL "OK",   1, "button", BS_DEFPUSHBUTTON | WS_TABSTOP | WS_CHILD, 9,
  3341.    66, 28, 14
  3342.    CONTROL "Cancel", 2, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD,  63,
  3343.    66, 32, 14
  3344.  END
  3345.  
  3346.  
  3347.  
  3348.  Figure 11. Graph Data Structure
  3349.  
  3350.  
  3351.  /*
  3352.   Data structure describing how we draw the graph
  3353.  */
  3354.  typedef struct tagGraphInfo
  3355.  {
  3356.   PRICE dwMinPrice;
  3357.   PRICE dwMaxPrice;
  3358.   DWORD dwScaleFactor;
  3359.   DWORD dwTickInterval;
  3360.   WORD iDenominator;  /* the fractional amount used for this stock */
  3361.   WORD iGridPen;
  3362.  } GRAPHINFO, FAR *LPGRAPHINFO;
  3363.  
  3364.  
  3365.  
  3366.  Figure 12. Tagging a Stock File
  3367.  
  3368.  
  3369.  #define MAXSTOCKNAME    5
  3370.  #define MAXDESCRIPTION    32
  3371.  #define MAXFILENAME        13
  3372.  
  3373.  typedef struct tagStockFile
  3374.  {
  3375.   DWORD dwMagic;
  3376.  #define MAGIC_COOKIE    66666666L
  3377.   char     szStock[MAXSTOCKNAME];
  3378.   char    szDescription[MAXDESCRIPTION];
  3379.   GRAPHINFO    graphinfo;
  3380.   WORD    nTicks;
  3381.  /*
  3382.   TICK  aTicks[1];
  3383.  */
  3384.  } STOCKFILE;
  3385.  
  3386.  
  3387.  
  3388.  Figure 13. Structure to Track Stock Information
  3389.  
  3390.  
  3391.  typedef struct tagInCoreStockInfo
  3392.  {
  3393.   char   szFileName[MAXFILENAME];    /* file name where the stock data is
  3394.         kept */
  3395.   STOCKFILE StockFile;    /* a copy of the stock file header  */
  3396.   HANDLE  hTicks;
  3397.   HWND   hWnd;    /* window in which stock is shown   */
  3398.   DWORD   dwFlags;    /* any kind of status bits we need
  3399.         to keep */
  3400.  #define STATE_HAS_VGRID 1L
  3401.  #define STATE_HAS_HGRID 2L
  3402.   HANDLE  hNextStockInfo;    /* link to next stock info struct   */
  3403.  } STOCKINFO, FAR *LPSTOCKINFO;
  3404.  
  3405.  
  3406.  
  3407.  Figure 14. Open Dialog Box Definition
  3408.  
  3409.  
  3410.  Open DIALOG 10, 10, 148, 112
  3411.   STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
  3412.   CAPTION "Open "
  3413.  BEGIN
  3414.   LTEXT "Open File &Name:",    IDC_FILENAME,    4,    4,  60,  10
  3415.   EDITTEXT    IDC_EDIT,    4,    16, 100, 12, ES_AUTOHSCROLL
  3416.   LTEXT "&Files in",    IDC_FILES,    4,    40, 32,  10
  3417.   LISTBOX,    IDC_LISTBOX,    4,    52, 70,  56, WS_TABSTOP
  3418.   LTEXT "",    IDC_PATH,    40,    40, 100, 10
  3419.   DEFPUSHBUTTON "&Open" ,    IDOK,    87,    60, 50,  14
  3420.   PUSHBUTTON "Cancel",    IDCANCEL,    87,    80, 50,  14
  3421.  END
  3422.  
  3423.  
  3424.  
  3425.  Figure 16. Processing the WM_COMMAND Message
  3426.  
  3427.  
  3428.  case WM_COMMAND:
  3429.   switch (wParam)
  3430.   {
  3431.  
  3432.    case IDC_LISTBOX:
  3433.      switch (HIWORD(lParam))
  3434.      {
  3435.       case LBN_SELCHANGE:
  3436.        if (!DlgDirSelect(hDlg, str, IDC_LISTBOX))
  3437.        {
  3438.         SetDlgItemText(hDlg, IDC_EDIT, str);
  3439.         SendDlgItemMessage(hDlg,
  3440.                         IDC_EDIT,
  3441.                         EM_SETSEL,
  3442.                         NULL,
  3443.                         MAKELONG(0, 0x7fff));
  3444.        }
  3445.        else
  3446.        {
  3447.         strcat(str, DefSpec);
  3448.         DlgDirList(hDlg, str, IDC_LISTBOX, IDC_PATH, 0x4010);
  3449.        }
  3450.        break;
  3451.  
  3452.       case LBN_DBLCLK:
  3453.        goto openfile;
  3454.      }
  3455.      return TRUE;
  3456.  
  3457.  
  3458.  
  3459.  Figure 17. Opening a File When OK Is Pressed
  3460.  
  3461.  
  3462.  
  3463.  
  3464.       case IDOK:
  3465.  openfile:
  3466.        GetDlgItemText(hDlg, IDC_EDIT, OpenName, 128);
  3467.        if (strchr(OpenName, '*') || strchr(OpenName, '?'))
  3468.        {
  3469.         SeparateFile(hDlg, (LPSTR) str, (LPSTR) DefSpec,
  3470.                      (LPSTR) OpenName);
  3471.         if (str[0])
  3472.          strcpy(DefPath, str);
  3473.         ChangeDefExt(DefExt, DefSpec);
  3474.         UpdateListBox(hDlg);
  3475.         return TRUE;
  3476.        }
  3477.        if (!OpenName[0])
  3478.        {
  3479.         MessageBox(hDlg, "No filename specified.", NULL,
  3480.                    MB_OK | MB_ICONHAND);
  3481.         return TRUE;
  3482.        }
  3483.  
  3484.        AddExt(OpenName, DefExt);
  3485.  
  3486.        /* The routine to open the file would go here, and the */
  3487.        /* handle would be returned instead of NULL.      */
  3488.        StockFileRead((LPSTR) OpenName);
  3489.  
  3490.        EndDialog(hDlg, hFile);
  3491.        return (TRUE);
  3492.  
  3493.       case IDCANCEL:
  3494.        EndDialog(hDlg, NULL);
  3495.        return (TRUE);
  3496.       }
  3497.       break;
  3498.  
  3499.    }
  3500.    return FALSE;
  3501.  }
  3502.  
  3503.  
  3504.  
  3505.  Questions & Answers - Windows
  3506.  
  3507.  Q:
  3508.  
  3509.  I'm curious about what a window handle (hWnd) object really is physically.
  3510.  Is it a global memory handle? A segment or selector? An array index?
  3511.  
  3512.  Josh Trupin
  3513.  Stamford, CT
  3514.  
  3515.  A:
  3516.  When you call CreateWindow or CreateWindowEx, you are calling functions in
  3517.  the Windows USER.EXE dynamic-link library (DLL). These functions perform the
  3518.  following call:
  3519.  
  3520.  LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, 64)
  3521.  
  3522.  and then a LocalLock, resulting in a NEAR pointer (PSTR) to 64 bytes inside
  3523.  of USER's data segment (DGROUP). This pointer is returned as a window handle
  3524.  (HWND) after USER fills out the 64 bytes that it refers to.
  3525.  
  3526.  To fill out this 64-byte "window object," USER actually employs an internal
  3527.  structure similar to the following.
  3528.  
  3529.   typedef struct  tagWND //       bytes
  3530.                          //       from
  3531.    {                     //length  end  GetWindow...
  3532.                          //------ ----- -------------
  3533.  
  3534.    WORD    Undocumented[22]; // 44  -64
  3535.    DWORD   ExStyle;          //  4  -20 GWL_EXSTYLE
  3536.    DWORD   Style;            //  4  -16 GWL_STYLE
  3537.    WORD    ID;               //  2  -12 GWW_ID
  3538.    WORD    hWndText;         //  2  -10 GWW_HWNDTEXT
  3539.    HWND    hWndParent;       //  2   -8 GWW_HWNDPARENT
  3540.    HANDLE  hInstance;        //  2   -6 GWW_HINSTANCE
  3541.    FARPROC WndProc;          //  4   -4 GWL_WNDPROC
  3542.    }  WND;                   // 64
  3543.   typedef WND NEAR *PWND;
  3544.  
  3545.  The elegance in this design is that when you make USER calls (functions
  3546.  whose first argument is usually an HWND), hWnd is cast inside of USER as a
  3547.  PWND so that the above WND structure elements can be addressed directly;
  3548.  that is, the following refers to the window's parent.
  3549.  
  3550.  ((PWND)hWnd)->hWndParent
  3551.  
  3552.  Code that refers to the window structures is therefore
  3553.  very efficient.
  3554.  
  3555.  Some of the arguments in the CreateWindow or CreateWindowEx call are placed
  3556.  directly in the structure; others are determined by USER.
  3557.  
  3558.  ((PWND)hWnd)->ExStyle    =dwExStyle;
  3559.  ((PWND)hWnd)->WndProc    =<WndProc from WNDCLASS of ClassName>;
  3560.  ((PWND)hWnd)->hWndText    =<another local pointer to WindowName> ;
  3561.  ((PWND)hWnd)->Style    =dwStyle;
  3562.  ((PWND)hWnd)->hWndParent    =hWndParent;
  3563.  ((PWND)hWnd)->ID    =hMenu; //if child window,=child ID
  3564.  ((PWND)hWnd)->hInstance    =hInstance;
  3565.  
  3566.  If your window class has extra bytes requested (specified by cbWndExtra in
  3567.  the WNDCLASS structure of the RegisterClass call), then additional bytes are
  3568.  allocated at the end of the 64-byte WND structure in the LocalAlloc call. In
  3569.  other words, the LocalAlloc call that USER performs is really as follows.
  3570.  
  3571.  LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, 64 +
  3572.             <cbWndExtra from WNDCLASS of ClassName> )
  3573.  
  3574.  This allows you to attach (with SetWindowWord and SetWindowLong)
  3575.  window-specific information to the window in an object-oriented way in your
  3576.  application.
  3577.  
  3578.  From all of the above you can see how SetWindowWord, SetWindowLong,
  3579.  GetWindowWord, and GetWindowLong work. When you make these calls, USER adds
  3580.  64 bytes to the hWnd pointer and then adds the nIndex.
  3581.  
  3582.  (PSTR)hWnd + sizeof(WND) + nIndex
  3583.  
  3584.  The resulting local offset refers to the value you are setting or getting
  3585.  with SetWindowWord and SetWindowLong and GetWindowWord and GetWindowLong.
  3586.  This is how application-specific extra window information is referred to
  3587.  with a base index of 0, and why the GWW_XXX and GWL_XXX indexes have
  3588.  negative offsets.
  3589.  
  3590.  There is little to no protection when using window handles, because no
  3591.  protected-mode selectors are being used. It is very easy to corrupt USER's
  3592.  DGROUP by using invalid window handles. A bad window handle could cause USER
  3593.  to corrupt its DGROUP without a protection violation occurring. Also, it is
  3594.  very easy to see how you could use another application's valid window
  3595.  handles to modify its window structures.
  3596.  
  3597.  Finally, you can see that window handles are reused. Since they are just
  3598.  offsets to structures that are created and destroyed, it is possible that
  3599.  another CreateWindow or CreateWindowEx call could return the same "handle"
  3600.  of a previously destroyed window, if the LocalAlloc resulted in the same
  3601.  physical offset inside USER's DGROUP.
  3602.  
  3603.  Therefore, a window handle is really an offset into USER's DGROUP at which a
  3604.  structure exists that defines a window. If you're still curious, you can
  3605.  look at these structures using HeapWalker. Just perform Object LocalWalk and
  3606.  Object Show operations on USER DATA and look for 68-byte memory objects.
  3607.  They're shown by HeapWalker as 68 bytes because of the additional 4 bytes at
  3608.  the beginning of every local memory object that KERNEL uses to manage local
  3609.  heaps.
  3610.  
  3611.  Q:
  3612.  I've written an application that uses more than 100 child windows. The
  3613.  windows are created when the application starts, but only some of them are
  3614.  visible at the same time. I have two problems.
  3615.  
  3616.  First, I can only run a few instances of my application. When Free System
  3617.  Resources (as reported in the Program Manager's About box, for example) gets
  3618.  close to 0% I get failures trying to create new windows. I have tons of
  3619.  memory, so I don't understand why this is happening.
  3620.  
  3621.  Second, it seems as if my application is very slow to initialize. I'm in the
  3622.  dark on this, since I can't find any documentation on what would cause it to
  3623.  be slow.
  3624.  
  3625.  Michael J. Paschal
  3626.  Waterbury, CT
  3627.  
  3628.  A:
  3629.  Windows maintains all windows inside the USER.EXE DLL. USER's data segment
  3630.  (DGROUP), like all C medium-model DGROUPs, is limited to 64Kb in size. All
  3631.  windows that are created consume space inside this DGROUP, as do menus,
  3632.  title-bar names, and so on, so you are bumping up against the 64Kb
  3633.  segment-size limit. The Free System Resources percentage reflects this
  3634.  constraint. The amount of global memory you have is irrelevant; the DGROUP
  3635.  can only grow to 64Kb.
  3636.  
  3637.  If you want to be able to start many instances of your application, you will
  3638.  have to change your window creation strategy. If you can get by creating
  3639.  your windows only as needed (Program Manager does this for its icon and
  3640.  application-name windows), you should operate that way. If you can destroy
  3641.  windows when they are not displayed, you should do that (although Program
  3642.  Manager doesn't). Hiding windows does not reduce their USER memory
  3643.  requirements.
  3644.  
  3645.  Your ability to do these things depends on your application. If you are
  3646.  creating all your windows in advance because you need to be positive you
  3647.  have them before you start, you are stuck with your current situation. If
  3648.  you can deal with a possible lack of windows further into run time, change
  3649.  your strategy.
  3650.  
  3651.  Each window you create requires a minimum of 68 bytes inside USER's 64Kb
  3652.  maximum DGROUP. In theory you could therefore have a maximum of about 960
  3653.  windows. But that estimation ignores the amount of static memory required by
  3654.  USER, any window text, any menus, and so on. I wrote a small program to test
  3655.  the maximum number of windows I could create. It turned out that the maximum
  3656.  number of really bare-bones windows I could create was around 700. Assuming
  3657.  you would more likely have some trimmings (menus, title-bar text), a more
  3658.  realistic estimate is 350.
  3659.  
  3660.  Your application is slow to start up because of KERNEL.EXE's local heap
  3661.  memory manager. Since local heaps start off being relatively small, KERNEL
  3662.  has to expand them as more local memory is requested; this takes a small
  3663.  amount of time. What is more time consuming is that LMEM_MOVEABLE objects
  3664.  inside USER's DGROUP have to be moved higher so that the LMEM_FIXED window
  3665.  structures can be created low. Your initialization is slow because KERNEL
  3666.  has to move the entire upper part (where LMEM_MOVEABLE objects are kept) of
  3667.  your USER DGROUP and adjust all the local handle table entries, doing this
  3668.  more than 100 times at start-up in your case.
  3669.  
  3670.  Q:
  3671.  I am creating a few edit class windows in my application by using
  3672.  CreateWindow. They are displayed in my main client area as children of my
  3673.  main window. The problem I am having is that the text that gets put in or
  3674.  created in these windows ends up being stored in my application's local
  3675.  heap, and space there has gotten very tight. Is there any way that edit
  3676.  class windows can be coerced into having their text stored in the global
  3677.  heap?
  3678.  
  3679.  B.D. Beykpour
  3680.  San Francisco, CA
  3681.  
  3682.  A:
  3683.  In edit controls in dialog boxes created with calls such as DialogBox and
  3684.  CreateDialog, edit control text is stored in the global heap by default. To
  3685.  have this text stored in the local heap, you must specify the DS_LOCALEDIT
  3686.  style.
  3687.  
  3688.  For edit class windows outside a dialog box that are created with
  3689.  CreateWindow, the reverse is true. By default, edit class window text is
  3690.  stored in the application's local heap. To force it to be stored in the
  3691.  global heap, use the following trick. When you call CreateWindow, replace
  3692.  the hInstance argument with a global handle to a small block of zeroed
  3693.  memory. Specifically, create the global memory object as follows.
  3694.  
  3695.  auto  GLOBALHANDLE  aGhEditBuffer;
  3696.  
  3697.  aGhEditBuffer=GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
  3698.                           256L);
  3699.  
  3700.  Then call CreateWindow with aGhEditBuffer as the hInstance argument. If you
  3701.  specify some initial text with the lpWindowName argument, it appears as the
  3702.  initial text in the edit window. The edit window operates as normal, but
  3703.  using the global heap for the edit text. The global memory block grows
  3704.  automatically as the text gets longer. When you destroy the edit window, the
  3705.  global memory buffer is automatically freed in the same way a local memory
  3706.  buffer is freed.
  3707.  
  3708.  Q:
  3709.  In WINDOWS.H a section labeled OEMRESOURCE appears to refer to the
  3710.  user-interface bitmaps, but I can't find much documentation on them. What
  3711.  are all these bitmaps and can I access them?
  3712.  
  3713.  Alex Polozoff
  3714.  Austin, TX
  3715.  
  3716.  A:
  3717.  Figure 1 identifies these bitmaps and shows which version of Windows they
  3718.  were developed for. The resolution of each bitmap depends on the display
  3719.  driver you have installed. In the Windows environment, Versions 1.x and 2.x,
  3720.  these were monochrome bitmaps; in Windows1 3.0 many of them are full-color
  3721.  format bitmaps, although only the colors black, white, and gray are actually
  3722.  used. In some cases (the Close bitmaps, for example) the bitmap really
  3723.  contains two or more equally-sized bitmaps arranged in rows; these
  3724.  "sub-bitmaps" would have to be extracted if you wanted to use them.
  3725.  
  3726.  The following code, which loads OBM_ZOOM, is an example of how to access
  3727.  these bitmaps:
  3728.  
  3729.      auto    HDC        ahDc;
  3730.      auto    HBITMAP    ahBm;
  3731.      auto    BITMAP     aBm;
  3732.  
  3733.      ahDc = CreateCompatibleDC(0);
  3734.      ahBm = LoadBitmap(0, MAKEINTRESOURCE(OBM_ZOOM));
  3735.      GetObject(ahBm, sizeof(aBm), (LPSTR)&aBm);
  3736.      /* aBm now has the bmWidth and bmHeight for
  3737.         BitBlt'ing */
  3738.      ahBm = SelectObject(ahDc, ahBm);
  3739.  
  3740.      /* the OBM_ZOOM bitmap can now be BitBlt'ed
  3741.         here from ahDc using the bmWidth and bmHeight
  3742.         in aBm */
  3743.  
  3744.      ahBm = SelectObject(ahDc, ahBm);
  3745.      DeleteObject(ahBm);
  3746.      DeleteDC(ahDc);
  3747.  
  3748.  Q:
  3749.  I have a child window that has a title bar in my client area. The child
  3750.  window looks similar to the PageMaker Toolbox window. The title bar is meant
  3751.  simply to label the window, though; I do not want the user to be able to
  3752.  move the child window by grabbing the title bar with the mouse. How do I
  3753.  lock down a child window that has a title bar?
  3754.  
  3755.  Frank Mena
  3756.  San Jose, CA
  3757.  
  3758.  A:
  3759.  What you want to do is prevent Windows from processing the move system
  3760.  command for the child. The following code fragment, which would be placed at
  3761.  the end of the child's WndProc message switch, shows how to do this.
  3762.  
  3763.  switch (awMsg)
  3764.       {
  3765.      .
  3766.      .
  3767.      .
  3768.       case WM_SYSCOMMAND:
  3769.            if ((awParam & 0xFFF0) == SC_MOVE)
  3770.                break;
  3771.       default:
  3772.           return DefWindowProc(ahWnd, awMsg, awParam,
  3773.                                alParam);
  3774.       }
  3775.   return 0L;
  3776.  
  3777.  As you can see in this code, all system commands except SC_MOVE are
  3778.  processed by DefWindowProc. By not
  3779.  
  3780.  sending the WM_SYSCOMMAND/SC_MOVE message to DefWindowProc, you will prevent
  3781.  the default processing of this message, which is for Windows to move the
  3782.  child window. This technique can be used to disable any WM_SYSCOMMAND
  3783.  subcommands.
  3784.  
  3785.  Reader Feedback
  3786.  
  3787.  Regarding the fourth question in the September Windows Q&A, MSJ (Vol. 5, No.
  3788.  5), Dean White of Dallas, Texas, notes that the following
  3789.  
  3790.  rem >%temp%\dosapp.sem
  3791.  
  3792.  accomplishes the same end and is better than
  3793.  
  3794.  echo %0 >%temp%\dosapp.sem
  3795.  
  3796.  He's right. Not only is no disk sector space wasted with his method, it is
  3797.  faster. A file entry with a length of zero is all that is created as a
  3798.  semaphore in the temporary files subdirectory.
  3799.  
  3800.  1  For ease of reading, "Windows" refers to the Microsoft Windows graphical
  3801.  environment. "Windows" refers only to this Microsoft product and is not
  3802.  intended to refer to such products generally.
  3803.  child window. This technique can be used to disable any WM_SYSCOMMAND
  3804.  subcommands.
  3805.  
  3806.  
  3807.